标签:过多 拷贝 语句 ima linu 开闭原则 委托 硬编码 his
在对象创建的过程中,经常会出现的一个问题就是通过显示地指定一个类来创建对象,从而导致紧耦合。这是因为创建对象时指定类名将使你受特定实现的约束而不是特定接口的约束。这会使未来的变化更加复杂。要避免这种情况,就应该间接地创建对象。
这种紧耦合的问题很大程度是由new关键字带来的,由于new的紧耦合出现,使得紧耦合的类很难独立地被复用,因为它们之间是相互依赖的。并且紧耦合产生单块的系统,要改变或者删掉一个类,就必须要理解和改变其他许多类。这也是导致系统难以维护和移植的一个重要原因。
所以可以通过“对象创建”模式绕开new,从而避免在对象创建(new)过程中所导致的紧耦合(依赖具体的类),以此支持对象创建的稳定。
那么如何避免new呢?举个例子!
public void fun1(){
//...
Product p = new Product(); //改为:Product p = productFactory.createProduct();
//...
}
这样的方式就是通过一个工厂调用一个方法来创建相应的产品,但是可能大家又会产生一个问题,这样操作虽然解决了Product
的new操作,但是对于ProductFactory
而言不是也需要通过new来产生吗?
对于这个问题,我想是很多人在接触到设计模式的时候都会去思考的问题,既然ProductFactory
还是要用到new,那工厂类还有存在的必要吗?这时,我们可以会想到两种解决方式,一是将createProdyct()
方法写成静态方法,这样调用的时候自然不需要new了。二是通过注入的方式,比如在应用类当中通过setter或是构造方法传入一个工厂类的对象。
对于静态方法而言,简单地说,即使是使用静态方法,那Product p = ProductFactory.createProduct()
这样依然是一种紧耦合的方式,因为工厂类无法替换,和直接new出产品区别不大。
对于注入方式,大家更多的是疑惑,既然可以传入一个工厂类对象,那为什么不直接传入相应的产品,不是更简单直接吗?当然不是的,首先需要明白的是,工厂类的作用是作为一个笼子,这个笼子需要帮助我们束缚住 ‘未来的变化’ ,要知道一个产品的变化可能总是大于工厂的变化。在这种情况下,举出一个最简单的例子,你在编码的过程中,可能会用到不只一个产品,那你就可能需要很多setter或者修改构造方法;但是如果这些产品都可以通过这个工厂来获取,是不是就相当于用笼子关住了变化,使其在一个范围中跳动。
在学习设计模式时,永远要记住的一句话就是“设计模式是用来教会我们如何应对未来可能的变化”。如果你能够确定自己的系统未来没有变化,那自然用不到设计模式;或者你的系统未来全是变化,那也用不到设计模式,设计模式做的就是隔离稳定与变化,如果没有稳定,那就用不到设计模式。
‘new’是一种硬编码,究竟 ’硬‘ 在那里,同样一个简单的理由,如果未来构造方法发生变化或者说构造参数增加(减少),而在源码中有很多地方都是通过new来获取实例对象,找到并修改源码将会是一项很大的工作。
在解决这样的 “对象创建” 问题中就有工厂方法、抽象工厂、原型模式和建造者模式等相关设计模式。
意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类。FactoryMethod使得一个类的实例化延迟到其子类。
实例
Factory Method相对于简单工厂而言,完全遵循了“不改代码”的原则,但是其使用情形相比抽象工厂使用条件没有那么高,因此可以说是使用最多的创建型模式之一了。
考虑这样一个应用,它可以向用户显示多种文档,比如word、pdf、txt等等。在这个框架中,首先,想到的可能就是应用简单工厂模式。
public interface Document{
public void open();
public void close();
public void save();
// ......
}
public class PdfDocument implements Document{
@Override
public void open(){
//open pdfDocument code
System.out.println("open pdf!");
}
@Override
public void close() {
System.out.println("close pdf!");
}
@Override
public void save() {
System.out.println("save pdf!");
}
// ......
}
public class TxtDocument implements Document{
//Txt实现代码,同PdfDocument
......
}
public class DocumentFactory{
public Document createDocument(String type){
if(type=="pdf"){
return new PdfDocument();
}else if(type=="txt"){
return new TxtDocument();
}else {
return null;
}
}
}
//简单工厂模式在客户类当中的调用
public class Client {
public static void main(String[] args) {
DocumentFactory factory
= new DocumentFactory();
Document pdfDocument
= factory.createDocument("pdf");
pdfDocument.open();
pdfDocument.save();
pdfDocument.close();
Document txtDocument
= factory.createDocument("txt");
txtDocument.open();
txtDocument.save();
txtDocument.close();
}
}
这样简单工厂模式,在不考虑未来新文档类型的情况下,确实是一种不错的实现方法。但是在后续的扩展过程当中,如果需要增加新的文档类,就需要去修改DocumentFactory
中的createDocument()
方法,增加新的类别,并且客户还必须知道这些类别才能使用。
为了应对这种情况,就出现了工厂方法。工厂方法就直接将工厂抽象出来,每个产品对应一个工厂,消除工厂模式中的条件分支结构(其实还有一种消除条件语句的模式,就是之前“组件协作”当中的策略模式)。
//Document部分不变
public interface Document{
public void open();
public void close();
public void save();
......
}
public class PdfDocument implements Document{
public void open(){
//open pdfDocument code
}
// close 和 save
......
}
public class TxtDocument implements Document{
//Txt实现代码
......
}
//并且后续可以扩展新的文档类
......
//修改factory部分如下
public interface DocumentFactory{
public Document createDocument();
}
public class PdfDocumentFactory
implements DocumentFactory {
@Override
public Document createDocument() {
return new PdfDocument();
}
}
public class TxtDocumentFactory
implements DocumentFactory {
@Override
public Document createDocument() {
return new TxtDocument();
}
}
//如果后续有新的产品,直接再实现DocumentFactory,得到新的工厂
......
//调用过程可做如下修改:
public class Client {
public static void main(String[] args) {
//利用多态性质,直接生成相应的factory子类
//消除了控制耦合
DocumentFactory factory = new PdfDocumentFactory();
Document pdfDocument
= factory.createDocument();
pdfDocument.open();
pdfDocument.save();
pdfDocument.close();
factory = new TxtDocumentFactory();
Document txtDocument
= factory.createDocument();
txtDocument.open();
txtDocument.save();
txtDocument.close();
}
}
有人可能会有疑问,这样不是还没完全消除new吗?首先这里的客户类已经到最高的调用层次了,这个过程当中是必然会有new的出现,不然怎样进行程序调用呢?
我们所说的消除new的过程是指main与factory之间,产生的一个中间层次(如下面的App)中去消除new。
//这样的代码中,就消除了new的存在
//具体的注入过程可以由其他的形式完成,比如Spring中的DI
public class App{
private DocumentFactory factory;
public void setFactory(DocumentFactory factory) {
this.factory = factory;
}
public void operateDoc(){
Document document = factory.createDocument();
document.open();
document.save();
document.close();
}
}
//main中的代码是最高层次,也是变化最频繁的层次,这里是不可能消除new的
public class Client {
public static void main(String[] args) {
DocumentFactory factory = new PdfDocumentFactory();
App app = new App();
app.setFactory(factory);
app.operateDoc();
//同样对于其他的工厂类也是可以采用同样的方式调用。
......
}
}
这样修改代码的好处在那里呢?第一,显而易见的就是完全实现了“开闭原则”的思想,扩展时不再需要去修改源码。第二,有些对象的创建过程可能比较复杂,因此如果直接在应用程序当中使用new或者其他形式创建很麻烦,通过工厂创建之后,就不再需要去关注那些复杂的创建过程。第三,通过new创建,始终是一种硬编码的形式,如果在应用程序当中过多的使用这种方式,那么一旦某对象的创建方式发生改变,修改源码必然是很繁琐的。
结构——类创建型模式
参与者
定义工厂方法中工厂创建的对象的接口。
ConcreteProduct(PdfDocument、TxtDocument)
实现Product的接口。
Creator(DocumentFactory)
声明工厂方法——createProduct(),可以调用该方法返回一个Product类型的对象。
ConcreteCreator(PdfDocumentFactory、TxtDocumentFactory)
重定义工厂方法以返回具体的ConcreteProduct。
Client(客户类)
使用工厂和产品,工厂方法模式中,客户类也是一个重要的参与者,因为工厂方法主要的作用就是分离开客户类与产品类之间的耦合关系,所以脱离客户类去谈工厂方法模式时,总会觉得差了些什么东西,无法完全体会到工厂方法模式的优势。
适用性
在下列情况下可以使用Factory Method模式:
简单地说,就是使用过程中只需要声明一个抽象工厂类的引用,具体调用那个工厂去生成那个对象,是由调用者去确定的。
相关模式
Abstract Factory经常用工厂方法来实现,抽象工厂创建产品的过程就可以使用工厂方法来完成。
工厂方法通常在Template Method中被调用,这一点在“组件协作”当中也提到过。
思考
意图
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
实例
假定存在这样一个服务层,该层当中需要做的就是访问数据库中的数据,并且执行一系列的相关操作。根据面向接口编程的思想,可以先作这样一个代码编写。
//对数据库进行访问的三个接口
//先建立连接,再执行相关操作,最后返回相应结果
public interface DBConnection{}
public interface DBCommand{}
public interface DBDataReader{}
//对于MySql,可以建立以下实现
public class MySqlDBConnection implements DBConnection{}
public class MySqlDBCommand implements DBCommand{}
public class MySqlDBDataReader implements DBDataReader{}
//同样对于Sql Server,Oricle也是这样的实现
......
这样的实现下,我们可以说是满足了面向接口编程的一个思想;并且在实现中,我们可以为每个接口,按照工厂方法模式,为其创建一个工厂。
//工厂接口
public interface DBConnectionFactory{
public DBConnection createDBConnetion();
}
public interface DBCommandFactory{
public DBCommand createDBCommand();
}
public interface DBDataReaderFactory{
public DBDataReader createDBDataReader();
}
//然后对于每个具体的数据库,实现不同的具体工厂
//以MySql为例
public class MySqlDBConnetionFactory implements DBConnectionFactory {
@Override
public DBConnection createDBConnetion() {
return new MySqlDBConnection();
}
}
public class MySqlDBCommandFactory implements DBCommandFactory {
@Override
public DBDBCommand createDBCommand() {
return new MySqlDBCommand();
}
}
public class MySqlDataReaderFactory implements DataReaderFactory {
@Override
public DBDataReader createDataReader() {
return new MySqlDataReader();
}
}
//剩下的Orcle,Sql Server也是如此
......
工厂模式方法的调用就不再演示,区别和工厂方法中的Document
例子中差别不大。
对于这样的实现,虽然我们很好的利用了工厂方法模式,但是也引入了工厂方法模式的一个弊端——大量的对象和类(本例当中,三个系列,每个系列三个产品,光产品就是9个子类;每个产品再对应一个工厂,一共就是18个子类)。在使用的过程中,反而能够明显的感觉到系统复杂度不减反增。并且,DBConnection
、DBCommand
和DBDataReader
明显是有着一定的关系的,换句话说,MySql建立的DBConnection
是和MySqlDBCommand、MySqlDBDataReader
一起使用的,如果出现MySqlDBConnection、OricleDBCommand、SqlServerDBDataReader
这种组合肯定是无法正常执行的。这时抽象工厂的出现,就很好的解决了这样的问题。
//首先,具体的产品类不会发生变化,简化的主要是工厂层次
//先抽象出抽象工厂,将产品系列的创建方法合并到一个接口中
public interface DBFactory{
public DBConnection createDBConnetion();
public DBCommand createDBCommand();
public DBDataReader createDBDataReader();
}
//根据不同的具体工厂,创建具体的对象
public class MySqlDBFactory implements DBFactory {
@Override
public DBConnection createDBConnetion() {
return new MySqlDBConnection();
}
@Override
public DBCommand createDBCommand() {
return new MySqlDBCommand();
}
@Override
public DBDataReader createDBDataReader() {
return new MySqlDBDataReader();
}
}
//Oricle,sql server的工厂,同样如此
......
抽象工厂主要是对工厂层次的简化,这样修改下来,对比工厂方法模式,减少了2/3的工厂子类创建,只需要3个工厂(有多少个产品系列就有多少个工厂子类)就可以完成产品的创建。
这样的一种创建工厂方式,不仅减少了工厂的数量,而且使得产品的一致性得以保证,它可以保证,一次只能使用同一个系列当中的对象。
public class Client {
public static void main(String[] args) {
DBFactory factory = new MySqlDBFactory();
App app = new App();
app.setFactory(factory);
app.operate();
//同样对于其他的工厂类也是可以采用同样的方式调用。
// ......
}
}
class App{
private DBFactory factory;
public void setFactory(DBFactory factory) {
this.factory = factory;
}
public void operate(){
DBConnection connection
= factory.createDBConnetion();
DBCommand command
= factory.createDBCommand();
DBDataReader reader
= factory.createDBDataReader();
//执行相关操作
.....
}
}
这样的应用程序代码,在一定程度上就减少了工厂子类的数量,并且在operate()
中保证了产品系列的一致性,使得MysqlDBFactory
生成的产品,只会是与MySql相关的。
结构——对象创建型模式
参与者
AbstractFactory(DBFactory)
声明一个创建抽象产品对象的操作接口。
ConcreteFactory(MySqlDBFactory)
实现创建具体产品对象的操作。
AbstractProduct(DBConnection、DBCommand、DBDataReader)
为一类产品对象声明一个接口。
ConcreteProduct(MySqlDBConection、MySqlDBCommand、MySqlDBDataReader)
定义一个将被相应的具体工厂创建的产品对象,并实现抽象产品的相应接口。
Client
调用抽象工厂和抽象产品提供的接口。在创建者模式当中,客户类也是重要的参与成员,因为对创建模式的理解容易混乱的点正是在客户类中的调用过程 (new) 产生的,关于这个问题,已经在前面做过很多解释了,不再多说。
适用性
以下情况可以使用AbstractFactory模式:
相关模式
Singleton:一个具体的工厂通常会是一个单件。因为在一个应用中,一般每个产品系列只需要一个具体工厂。
Factory Method:在Abstract Factory中,仅仅是声明一个创建对象的接口,真正的创建过程是由具体工厂实现的。这时,可以为每一个方法对应的具体对象之间再定义一个工厂方法。但其问题就在于,这样的做法就像是在工厂方法上再套上一层抽象工厂,从而又增加了系统的复杂度。
思考
DBFactory
接口,并且涉及到DBFactory
及其子类的改变。意图
用原型实例指定对象的创建种类,并且通过拷贝这些原型创建新的对象。
实例
Prototype模式,有点像是对工厂方法模式中产品与工厂的合并。怎么说呢?看下面的代码:
//工厂方法模式中的产品类与工厂方法类
public interface Document{
public void open();
public void close();
public void save();
......
}
public interface DocumentFactory{
public Document createDocument();
}
这是在Factory Method中使用的创建方式,而原型做的事就是,不再用工厂来进行创建,而是转而克隆的方式。变成下面这样:
//合并Document和DocumentFactory
public abstract class Document
implements Cloneable{
public void open();
public void close();
public void save();
......
//相当于Factory中的createDocument();
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
public class PdfDocument implements Document{
@Override
public void open(){
//open pdfDocument code
System.out.println("open pdf!");
}
@Override
public void close() {
System.out.println("close pdf!");
}
@Override
public void save() {
System.out.println("save pdf!");
}
//......
}
//文档类的实现与工厂方法中的一样
......
那么在具体的客户类当中就是通过这样一种方式来进行调用:
public class App{
//在工厂方法模式当中,这里是documentFactory
private Document prototype;
public void setDoucument(Document document){
prototype = document;
}
public void useDocument(){
//documentFactory.createDocument();
Document doc = prototype.clone();
//然后使用prototype克隆出来的doc进行操作
doc.open();
......
}
}
//在客户类中调用表现
public class Client {
public static void main(String[] args) {
Document doc = new PdfDocument();
App app = new App();
app.setFactory(doc);
app.useDocument();
//同样对于其他的工厂类也是可以采用同样的方式调用。
//......
}
}
问题来了,为什么不直接用原型(prototype),而是要多一步克隆?解决这个问题,首先要明白的是,我们使用原型的目的不是将原型作为客户类的一个属性去使用,而是一个创建者。既然是一个创建者,那么在使用的过程中,就不只一个地方会用到同样类型的对象;如果在不同的地方都直接使用原型,可能会在某个地方修改了原型的值,从而使得其他直接使用原型的方法出现不可预知的错误。
结构——对象创建型模式
参与者
Prototype(Document)
声明一个克隆自身的接口。
ConcretePrototype(PdfDocument...)
继承Prototype并实现克隆自身的接口。
Client
让一个原型克隆自身从而创建一个新的对象。
适用性
当一个系统应该独立于它的产品创建、构成和表示时,可以使用Prototype模式。
当要实例化的类是在运行时候指定时,比如动态装载。
为了避免创建一个产品类平行的工厂类层次时。
当一个类的实例只能有几种不同状态组合中的一种时,建立相应数目的原型并克隆他们可以比每次用合适的状态手工实例化该类更方便一些。
相关模式
Abstract Factory和Prototype在某种方面是相互竞争的,但是在某种情况下也是可以一起使用,比如,在抽象工厂中存储一个被克隆的产品集合,在使用时,直接根据集合中的对象返回相应的产品。
大量使用Composite(组合模式)和Decorator(装饰器模式)的设计上也可以采用Protorype来减少Composite或Decorator对象的创建。
思考
意图
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以有不同的表示。
实例
在游戏场景当中,尤其是3d场景中,必不可少就是建筑物,比如说房子。对房子的构建肯定不是一下全部构建完成的,而是会分成几个部分,比如墙、窗户、地板、房顶、门,一部分、一部分地去构建。
public abstract class House{
//房子属性,纹理、材质...
private Texture texture;
private Material material;
......
//墙、窗户、地板、房顶、门
public Wall buildWall();
public Window buildWindow();
public Floor buildFloor();
public Door buildDoor();
public Roof buildRoof();
//房子构建过程
public void buildHouse(){
buildFloor();
Wall[] walls = new Wall[4];
for(int i=0;i<walls.length;i++)
walls[i] = buildWall();
Window window = buildWindow();
wall[1].setWindow(window);
Door door = builDoor();
wall[2].setDoor(door);
buildRoof();
}
}
这种构建方式,采用的明显就是模板方法(Template Method),这种实现方式还可以根据不同的房子类型,实现具体的细节。
//石头屋
public class StoneHouse extends House{
//具体实现细节
.......
}
//茅草屋
public class ThatchedHouse extends House{
//具体实现细节
.......
}
//按照模板主方法在客户类中的调用形式表现:
public class Client{
public static void main(String[] args){
//只需要生成一个相应对象即可在游戏场景中完成相应类型房子的创建
House house = new StoneHouse();
house.buildHouse(); //生成石头屋
house = new ThatchedHouse();
house.buildHouse(); //生成茅草屋
}
}
这种实现有什么问题呢?类太臃肿了,对吧~这样的实现过程,可以体现复用的思想,但是问题之一就在于所有的内容全部都放在了一个类中,体现不出单一职责和类的信息与行为集中;这时就可以将创建过程分离出来,形成一个Builder,由这样一个Builder来专门负责创建。
//Director
public class House{
//房子属性,纹理、材质...
private Texture texture;
private Material material;
......
//增加builder的引入
private HouseBuilder houseBuilder;
public void setHouseBuilder(HouseBuilder hBuilder){
houseBuilder = hbuilder;
}
//房子构建过程
public void buildHouse(){
houseBuilder.buildFloor();
Wall[] walls = new Wall[4];
for(int i=0;i<walls.length;i++)
walls[i] = houseBuilder.buildWall();
Window window
= houseBuilder.buildWindow();
wall[1].setWindow(window);
Door door = houseBuilder.builDoor();
wall[2].setDoor(door);
houseBuilder.buildRoof();
}
}
//分离出来的builder
public interface HouseBuilder{
//墙、窗户、地板、房顶、门
public Wall buildWall();
public Window buildWindow();
public Floor buildFloor();
public Door buildDoor();
public Roof buildRoof();
}
public class StoneHouseBuilder
implements HouseBuilder{
//具体实现细节
.......
}
public class ThatchedHouseBuilder
implements HouseBuilder{
//具体实现细节
.......
}
//修改过后,在客户类中的调用形式表现:
public class Client{
public static void main(String[] args){
//只需要生成一个相应对象即可在游戏场景中完成相应类型房子的创建
House house = new House();
HouseBuilder builder
= new StoneHouseBuilder();
house.setHouseBuilder(builder);
house.buildHouse();
builder = new ThatchedHouseBuilder();
//这个set过程可以运行时完成
house.setHouseBuilder(builder);
house.buildHouse();
}
}
通过这样一种方式,实现复杂对象构建与表示的分离,并且,对于不同的房子对象,如果房子其他参数没有任何差别,就只需要传入相应的builder即可,而不需要再生成各种各样的子类(如StoneHouse、ThatchedHouse
)。
一旦生成对象,只需要修改其builder就可以马上改变其对象表示,而不需要新生成对象。并且这种修改过程,是可以动态完成的,就如果Spring当中的依赖注入过程一样,可以在运行时刻完成,而不一定是一开始就确定的
结构——对象创建型模式
参与者
Builder(HouseBuilder)
为创建一个Product对象的各个部件指定抽象接口。
ConcreteBuilder(StoneHouseBuilder、ThatchedHouseBuilder)
Director(House)
构造一个使用builder的对象。
Product(Wall、Window...)
包含了定义组成部件的类以及被构造的复杂对象等。
适用性
Builder的适用情况:
Builder模式更多的是体现的一种思想,而不是具体的过程,这种思想就是,当一个类的信息与行为过于臃肿时,也许可以采用Builder这种方式对类的信息与行为进行重新划分,从而使得类看起来更加的“轻” 。
相关模式
Abstract Factory和Builder都是对复杂对象的创建封装,但二者的区别在于,Builder着重于一步一步构建一个复杂对象,而Abstract Factory着重于多个系列产品对象的创建,并且系列对象之间有着某种联系。
Composite模式中对象通常情况下就是用Builder生成的。
思考
公众号:良许Linux
标签:过多 拷贝 语句 ima linu 开闭原则 委托 硬编码 his
原文地址:https://www.cnblogs.com/yychuyu/p/13281011.html