原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是使用常规的实例化过程。当对象创建成本高或创建过程很复杂时,通过复制一个现有的对象,然后基于复制出来的对象进行修改是一个非常好的方法。我们的学渣小明是使用原型模式的高手。期末考试了,学渣小明什么都不会,不慌,使用原型模式即可。
TypeScript
有一个试卷类 ExaminationPaper
,包含名字 string
、选择题 Question[]
和简答题 Question[]
三个属性:
ts
interface Prototype {
clone(): Prototype
}
class Question implements Prototype {
private answer: string
constructor(answer: string) {
this.answer = answer
}
setAnswer(answer: string) {
this.answer = answer
}
getAnswer(): string {
return this.answer
}
clone(): Prototype {
return new Question(this.answer)
}
}
class ExaminationPaper implements Prototype {
choiceQuestions: Question[]
shortAnswerQuestions: Question[]
name: string
constructor(
name: string,
choiceQuestions: Question[],
shortAnswerQuestions: Question[]
) {
this.name = name
this.choiceQuestions = choiceQuestions
this.shortAnswerQuestions = shortAnswerQuestions
}
clone(): Prototype {
return new ExaminationPaper(
this.name,
this.choiceQuestions.map((q) => q.clone() as Question),
this.shortAnswerQuestions.map((q) => q.clone() as Question)
)
}
print() {
console.log(this.name, 'paper:')
console.log(this.choiceQuestions.map((q) => q.getAnswer()))
console.log(this.shortAnswerQuestions.map((q) => q.getAnswer()))
}
}
const xiaohongPaper = new ExaminationPaper(
'xiaohong',
[new Question('A'), new Question('B')],
[new Question('answer1.'), new Question('answer2.')]
)
xiaohongPaper.print()
// Copy xiaohong's paper
const xiaomingPager = xiaohongPaper.clone() as ExaminationPaper
// Modify name
xiaomingPager.name = 'xiaoming'
// For short answer questions, add a closing word to the end
xiaomingPager.shortAnswerQuestions.forEach((q) =>
q.setAnswer(q.getAnswer() + `That's all, thanks!`)
)
xiaomingPager.print()
首先,小红实例化 ExaminationPaper
进行作答。然后,小明通过调用小红试卷的 clone
方法复制出一份新的实例,修改名字为 xiaoming
,同时为了避免雷同,还"机智"地在每道简答题答案后面加上了一段特有结束语。这样,小明不费吹灰之力就白嫖了一份答案。
从这个例子来看,原型模式很简单,关键在于实现 Prototype
的 clone
方法,并注意对复杂类型的属性进行深度拷贝。
而对于 Rust 来说,借助其强大的宏的特性,实现原型模式则更加方便。
Rust
rust
#[derive(Clone)]
struct Question {
answer: String,
}
impl Question {
fn new(answer: &str) -> Self {
Self {
answer: answer.to_string(),
}
}
fn get_answer(&self) -> &str {
self.answer.as_str()
}
fn set_answer(&mut self, answer: String) {
self.answer = answer
}
}
#[derive(Clone)]
struct ExaminationPaper {
choice_questions: Vec<Question>,
short_answer_questions: Vec<Question>,
name: String,
}
impl ExaminationPaper {
fn new(
name: &str,
choice_questions: Vec<Question>,
short_answer_questions: Vec<Question>,
) -> Self {
Self {
name: name.to_string(),
choice_questions,
short_answer_questions,
}
}
fn print(&self) {
println!("{} paper:", self.name);
println!(
"{}",
self.choice_questions
.iter()
.map(|q| q.get_answer())
.collect::<Vec<_>>()
.join(" ")
);
println!(
"{}",
self.short_answer_questions
.iter()
.map(|q| q.get_answer())
.collect::<Vec<_>>()
.join(" ")
);
}
}
fn main() {
let xiaohong_paper = &ExaminationPaper::new(
"xiaohong",
vec![Question::new("A"), Question::new("B")],
vec![Question::new("answer1."), Question::new("answer2.")],
);
xiaohong_paper.print();
let xiaoming_paper = &mut xiaohong_paper.clone();
xiaoming_paper.name = "xiaoming".to_string();
for q in xiaoming_paper.short_answer_questions.iter_mut() {
q.set_answer(format!("{} {}", q.get_answer(), "That's all. Thanks!"));
}
xiaoming_paper.print();
}
可以看到,我们并没有给 ExaminationPaper
和 Question
实现 clone
方法,而只是在类型前面加上了 #[derive(Clone)]
。
当在一个结构体或枚举类型上添加 #[derive(Clone)]
派生宏时,Rust 编译器会自动为该类型生成一个 Clone trait
的实现。
对于结构体类型,自动生成的 Clone trait
实现会逐个克隆每个字段,并返回一个新的结构体对象。这意味着每个字段都必须实现 Clone trait
或是基本类型(如整数、浮点数等),否则编译器会报错。
对于枚举类型,自动生成的 Clone trait
实现会逐个克隆每个变体,并返回一个新的枚举对象。同样地,每个变体中的字段都必须实现 Clone trait
或是基本类型。
这样通过使用 #[derive(Clone)]
,我们可以轻松地为自定义类型生成克隆功能,且无需手动实现 Clone trait
,可以减少很多样板代码。
欢迎关注公众号"前端游"