4.抽象工厂模式的UML
2.示例
2.1背景说明
对数据库的操作:连接-->打开-->使用-->关闭
对于不同的数据操作步骤都是一样的,只是连接数据库稍有不同
这里我们定义一个抽象模版类DBOperator,其中我们定义ConnDB()、OpenDB()、UseDB()、CloseDB()四个抽象方法
定义不同的数据库具体实现类:SQLServerDBOperator、OracleDBOperator
2.2代码实现
①定义抽象父类
publicabstractclassDBOperator { //原语操作1 publicabstractvoidConnDB(); //原语操作2 publicvoidOpenDB() { Console.WriteLine("打开数据库"); } //原语操作3 publicvoidUseDB() { Console.WriteLine("使用数据库"); } //原语操作4 publicvoidCloseDB() { Console.WriteLine("关闭数据库"); } //钩子方法:用于子类反向控制父类中某个方法是否执行 //注意钩子方法这里定义为虚方法,虚方法和抽象方法不同的地方就是虚方法有方法体,这样我们就可以在虚方法中定义默的方法 publicvirtualboolIsStart() { returntrue;//默认为true所以在具体子类中重写 } //模版方法:定义算法骨架 //确定其他基本方法的执行顺序 publicvoidProcess() { ConnDB(); OpenDB(); if(IsStart())//通过钩子方法控制是否执行UseDB() { UseDB(); } CloseDB(); } } |
//具体子类1 publicclassOracleDBOperator:DBOperator { publicoverridevoidConnDB() { Console.WriteLine("连接Oracle数据库"); } //按照面向对象的思想,我们可以在子类中使用new关键字覆盖父类中的方法 //注意: //1.这里覆盖了UseDB(),是不够的,还要覆盖调用这个方法的方法Process() //2.若是父类引用指向子类对象,则需要将父类转化为子类对象才可以使用子类中覆盖的方法 publicnewvoidUseDB() { Console.WriteLine("使用Oracle数据库"); } publicnewvoidProcess() { ConnDB(); OpenDB(); if(IsStart()) { UseDB(); } CloseDB(); } } //具体子类2 publicclassSQLServerDBOperator:DBOperator { ///具体子类继承抽象父类,重写抽象父类中的抽象方法 ///抽象父类中的非抽象方法就是每个子类都通用的方法, publicoverridevoidConnDB() { Console.WriteLine("连接SQLServer数据库"); } publicoverrideboolIsStart()//覆盖了钩子函数,修改了抽象父类中模版方法,实现子类反向控制父类 { returnfalse; } } |
staticvoidMain(string[]args) { staticvoidMain(string[]args) { DBOperatorsqlServerDBOperator=newSQLServerDBOperator(); DBOperatororacleDBOperator=newOracleDBOperator(); sqlServerDBOperator.Process();//注意这里,我们定义的钩子方法是虚方法,通过重写,实现子类控制父类,这里是不需要将强转为子类对象 Console.WriteLine("---------------------------"); oracleDBOperator.Process(); Console.WriteLine("---------------------------"); ((OracleDBOperator)oracleDBOperator).Process();//这里是通过覆盖父类中的方法,所以需要将父类强转为相应的子类对象 Console.WriteLine("---------------------------"); Console.ReadKey(); } } |
3.总结分析
3.1补充
1.模版方法模式中为何使用的是抽象类而不是接口?
这里先说明一下接口和抽象类的区别:
接口只是定义一组行为,某个类(非抽象类)实现这个接口就需要使用该接口中的所有方法。简而言之:接口约束实现该接口的类的行为。所以接口也称之为契约。
抽象类中的抽象方法需要子类重写,而抽象类可以有非抽象方法,非抽象方法是需要在抽象类中写方法体的。简而言之:抽象类不仅约束子类的行为而且需要为子类中非抽象方法提供方法体
这里我们使用抽象类而不是使用接口,为什么呢?若是定义为抽象方法则我们需要在实现类中需要对抽象方法都重写,而可能多个子类中的某个方法实现是一样的。
这里也体现了接口和抽象类的不同之处:接口约束实现接口的类行为(所以接口也称之为契约)而抽象类不仅要约束子类的行为,而要为子类提供公共方法体
这里示例中的数据库连接函数ConnDB定义为抽象方法,因为每个数据库的连接方法是不一样的,所以在每个具体的实现类中都需要对其重写。而其他的数据库操作方法类似,所以我们在父类中对其实现,避免子类中代码冗余。
3.2优点
模板方法模式的优点是实现代码复用。
模板方法模式是一种实现代码复用的很好的手段。通过把子类的公共功能提炼和抽取,把公共部分放到模板中去实现。
模版方法模式体现类开闭原则。首先模版方法模式从设计上分离变与不变,然后把不变的部分抽取出来,定义到父类中,比如算法骨架,一些公共的、固定的实现等。这些不变的部分被封闭起来,尽量不去修改它们。要想扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放的。
3.3缺点
模板方法模式的缺点是算法骨架不容易升级。
模板方法模式最基本的功能就是通过模板的制定,把算法骨架完全固定下来。事实上模板和子类是非常耦合的,如果要对模板中的算法骨架进行变更,可能就会要求所有相关的子类进行相应的变化。比如模版方法中添加一个抽象方法,则所有的子类都需要去实现这方法。
所以抽取算法骨架的时候要特别小心,尽量确保是不会变化的部分才放到模板中。
3.4适应场合
建议在以下情况中选用模板方法模式
需要固定定义算法骨架。实现一个算法的不变的部分,并把可变的行为留给子类来实现的情况。
各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复。
需要控制子类扩展的情况。模板方法模式会在特定的点来调用子类的方法,这样只允许在这些点进行扩展。
3.5对比与合用
【TODO】(待策略模式研究完在完成此部分!)
模版方法模式与工厂方法模式
模版方法模式和策略模式