老师在黑板上抄题目,我们要先抄题目,再做答案。请把抄题目的程序写出来。
第一版(重复=易错+难改)
代码结构图:
学生甲抄的试卷类:
// 学生甲抄的试卷
class TestPaperA{
// 试题1
public void TestQuestion1(){
Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
Console.WriteLine("答案:a");
}
// 试题2
public void TestQuestion2(){
Console.WriteLine("1+1=[] a.1 b.2 c 3 d.11");
Console.WriteLine("答案:b");
}
}
学生乙抄的试卷类:
// 学生乙抄的试卷
class TestPaperB{
// 试题1
public void TestQuestion1(){
Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
Console.WriteLine("答案:c");
}
// 试题2
public void TestQuestion2(){
Console.WriteLine("1+1=[] a.1 b.2 c 3 d.11");
Console.WriteLine("答案:b");
}
}
客户端代码:
static void Main(string[] args){
Console.WriteLine("学生甲抄的试卷:");
TestPaperA studentA = new TestPaperA();
studentA.TestQuestion1();
studentA.TestQuestion2();
studentA.TestQuestion3();
Console.WriteLine("学生乙抄的试卷:");
TestPaperB studentB = new TestPaperB();
studentB.TestQuestion1();
studentB.TestQuestion2();
studentB.TestQuestion3();
Console.Read();
}
有发现问题吗?
学生甲和学生乙两个抄试卷类非常类似,除了答案不同,没什么不一样。这样写又容易错,又难以维护。如果老师突然要改题目,或者某人抄错了,那就非常糟糕了。
其实,应该让老师出一份试卷,打印多份,让学生填写答案就可以了。在这里应该就是把试题和答案分享,抽象出一个父类,让两个子类继承于它,公共的试题代码写到父类当中,就可以了。
第二版
试卷父类代码:
// 金庸小说考题代码
class TestPaper{
public void TestQuestion1(){
Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
}
public void TestQuestion2(){
Console.WriteLine("1+1=[] a.1 b.2 c 3 d.11");
}
}
学生子类代码:
// 学生甲抄的试卷
class TestPaperA:TestPaper{
// 试题1
public new void TestQuestion1(){
base.TestQuestion1();
Console.WriteLine("答案:a");
}
// 试题2
public new void TestQuestion2(){
base.TestQuestion2();
Console.WriteLine("答案:b");
}
}
学生乙类似。
客户端代码完全相同,略。
这下子类就非常简单了,只需要填写答案就可以。然而,这还只是初步的泛化,子类中仍有重复的代码,如“base.TestQuestion1();”、“ Console.WriteLine("答案:");”,——似乎除了选项的abcd,其他都是重复的。
我们既然用了继承,并且肯定这个继承有意义,就应该要成为子类的模版。所有重复的代码都应该要上升到父类去,而不是让每个子类都去重复。
模板方法登场了——
当我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模板方法模式来处理。
刚才已经分析,在初始各学生的试卷中,只有答案选项可能不同,其他全部是一样的。
于是我们就改动这里,增加一个虚方法:
public void TestQuestion1(){
Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
Console.WriteLine("答案:" + Answer1()); // 改成一个虚方法
}
// 此方法的目的就是给继承的子类重写,
// 因为这里每个人的答案都是不同的
protected virtual string Answer1(){
return "";
}
其余两个题目也用相同的做法。
然后子类就非常简单了,重写虚方法后,把答案填上,其他什么都不用管,因为父类建立了所有重复的模版。
// 学生甲抄的试卷
class TestPaperA : TestPaper{
protected override string Answer1(){
return "a";
}
protected override string Answer2(){
return "b";
}
}
// 学生乙类似
代码结构图:
客户端代码需要改动一个小地方,即原来是子类变量的声明,改成了父类。这样就可利用多态性实现代码的复用了。
static void Main(string[] args){
Console.WriteLine("学生甲抄的试卷:");
// 将子类变量的声明改成了父类,利用了多态性,实现了代码的复用
TestPaper studentA = new TestPaperA();
studentA.TestQuestion1();
studentA.TestQuestion2();
studentA.TestQuestion3();
Console.WriteLine("学生乙抄的试卷:");
TestPaper studentB = new TestPaperB();
studentB.TestQuestion1();
studentB.TestQuestion2();
studentB.TestQuestion3();
Console.Read();
}
此时如有更多的学生来答试卷,只不过是在试卷的模版上填写选择题的选项答案,这是每个人试卷的唯一不同。(还有姓名)
模板方法模式
模板方法模式,定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
AbstractClass是抽象类,其实也就是一抽象模板,定义并实现了一个模版方法。这个模版方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
abstract class AbstractClass{
// 一些抽象行为,放到子类去实现
public abstract void PrimitiveOperation1();
public abstract void PrimitiveOperation2();
// 模版方法,给出了逻辑的骨架,而逻辑的组成是一些相应的抽象操作。
// 它们都推迟到子类实现
public void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
Console.WriteLine("");
}
}
ConcreteClass,实现父类所定义的一个或多个抽象方法。每一个AbstractClass都可以有任意多个ConcreteClass与之对应,而每一个ConcreteClass都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
class ConcreteClassA : AbstractClass{
// 与ConcreteClassB不同的方法实现
public override void PrimitiveOperation1(){
Console.WriteLine("具体类A方法1实现");
}
public override void PrimitiveOperation2(){
Console.WriteLine("具体类A方法2实现");
}
}
class ConcreteClassB : AbstractClass{
// 与ConcreteClassA不同的方法实现
public override void PrimitiveOperation1(){
Console.WriteLine("具体类B方法1实现");
}
public override void PrimitiveOperation2(){
Console.WriteLine("具体类A方法2实现");
}
}
客户端调用:
static void Main(string[] args){
AbstractClass c;
c = new ConcreteClassA();
c.TemplateMethod();
c = new ConcreteClassB();
c.TemplateMethod();
Console.Read();
}
总结
模板方法模式是通过把不变行为搬移到超类,去除子类中的重复代码来体现它的优势。其实,模板方法模式就是提供了一个很好的代码复用平台。
本章完。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 本文是连载文章,此为第八章,学习定义一个操作中的算法的骨架,而把具体实现细节留给子类的模板模式。
上一章:https://blog.youkuaiyun.com/qq_36770641/article/details/82804903 原型模式
下一章:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------