创建型模式

您所在的位置:网站首页 周报模式 创建型模式

创建型模式

2023-12-26 23:10| 来源: 网络整理| 查看: 265

创建型模式

创建型模式对类的实例化过程进行了抽象,能够将软件中对象的创建和使用分离,使整个系统的设计更加符合单一职责原则。

image-20230102063333172

什么是对象的创建与对象的使用分离?

image-20230102063352572

一个女生想吃苹果,怎么办?

image-20230102063410061

对象的创建和对象的使用分离

第一种类型

Student s = new Student() s.study

第二种类型

Student s = Factory.Create() s.study()

创建型模式的目的是将对象的创建与使用分离,这样做有什么好处?

创建型模式在创建什么(what,由谁创建who,何时创建when等方面都为软件设计者提供了尽可能大的灵活性,创建型模式隐藏了类的市里的创建西街,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的)

image-20230102063636870

简单工厂模式 模式动机

只需要知道水果的名字则可以得到相应的水果

image-20230102064023252

只需要知道类的名字就可以得到相应的对象

image-20230102064035533

简单工厂模式又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式在简单工厂模式中,可以根据参数的不同返回不同类的实例在简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类 模式结构

简单工厂模式包含如下角色

Factory:工厂角色

Product:抽象产品角色

ConcreateProduct:具体产品角

image-20230102064456320

abstract class product{ //所有产品类的公共业务方法 public void methodSame(){ //公共方法的实现 } //声明抽象业务方法 public abstract void methodDiff(); } class Factory{ //静态工厂方法 public static Product getproProducts(String arg){ Product product = null; if(arg.equalsIgnoreCASE("A")){ product = new CreateProductA(); //初始设置product }else if(arg.equalsIgnoreCase("B")){ product = new ConcreteProductB(); //初始化设置product } return product; } } class ConcreateProduct extends Product{ //实现业务方法 public void methodDiff(){ //业务方法的实现 } } class Client{ psvm{ Product product; product = Factory.getProduct("A");通过工厂类创建产品对象 prducthodSame(); producthodDiff(); } }

Sunny软件公司基于java语言开发一套图标库,该图标库可以为应用系统提供不同外观的图表,例如柱状图,饼状图折线图等,sunny软件公司图标库设计人员提出了一个初始设计方案,将所有图标的实现代码封装在一个chart类,其框架代码如下所示

class Chart{ private String type;//图标类型 public Chart(Object[][] data,String type){ this.type = type; if(type.equalsIgnoreCase("histogram")){ //初始化柱状图 }else if(type.equalsIgnoreCase("pie")){ //初始化饼状图 }else if(type.equalsIgnoreCase("line")){ //初始化折线图 } } } public void display() { if (this.type.equalsIgnoreCase(“histogram”)) {//显示柱状图 } else if (this.type.equalsIgnoreCase(“pie”)) {//显示饼状图} else if (this.type.equalsIgnoreCase(“line”)) {//显示折线图} }

客户端代码通过调用Chart类的构造函数来创建图表对象, 根据参数type的不同可以得到不同类型的图表, 然后再调用display()方法来显示相应的图表。

chart类是一个巨大的类,在该类的设计中存在如下几个问题:

(1)在Chart类中包含很多if…else…”代码块,整个类的代码相当冗长,代码越长,阅读难度、维护难度和测试难度也越大; 而且大量条件语句的存在还将影响系统的性能, 程序在执行过程中需要做大量的条件判断。

(2) Chart类的职责过重, 它负责初始化和显示所有的图表对象, 将各种图表对象的初始化代码和显示代码集中在一个类中实现,违反了单一职责原则, 不利于类的重用和维护; 而且将大量的对象初始化代码都写在构造函数中将导致构造函数非常庞大, 对象在创建时需要进行条件判断, 降低了对象创建的效率。

(3) 当需要增加新类型的图表时, 必须修改Chart类的源代码**, 违反了“开闭原则”**。

(4) 客户端只能通过new关键字来直接创建Chart对象, Chart类与客户端类耦合度较高, 对象的创建和使用无法分离。

简单工厂

为了将chart类的职责分离,同时将chart对象的创建和使用分离,sunny软软件开发供人缘决定使用简单工程模式对图标库进行重构,重构后的结构如图所示。

image-20230102070740551

image-20230102070800261

//抽象图标接口:抽象产品类 interface chart{ public display(); } //柱状图类:具体产品类 class HistogramChart implements Chart{ public HistogramChart(){ sout("创建柱状图!"); } public void display(){ sout("显示柱状图"); } } //折线图类:具体产品类 class PieChart implements Chart{ public PieChart(){ sout("创建饼状图!"); } public void display(){ sout("显示饼状图"); } } //折线图类:具体产品类 class LineChart implements Chart{ public HistogramChart(){ sout("创建折线图!"); } public void display(){ sout("创建折线图!"); } } class Client { public static void main(String args[]) { Chart chart; //通过静态工厂方法创建产品 chart = ChartFactory.getChart("histogram"); chart.display(); }} //图表工厂类: 工厂类 class ChartFactory { //静态工厂方法 public static Chart getChart(String type) { Chart chart = null; if (type.equalsIgnoreCase("histogram")) { chart = new HistogramChart(); System.out.println("初始化设置柱状图! "); } else if (type.equalsIgnoreCase("pie")) { chart = new PieChart(); System.out.println("初始化设置饼状图! "); else if (type.equalsIgnoreCase("line")) { chart = new LineChart(); System.out.println("初始化设置折线图! "); } return chart; }}

可以将静态工厂方法的参数存储在XML或properties格式的配置文件中, 如下config.xml所示:

image-20230102071627195

在简单工厂模式中增加新的具体产品时是否符合*“开闭原则”*? 如果不符合, 原有系统需作出哪些修改?

案例:女娲造人:《风俗通》中说俗话开天辟地,未有人民,女娲黏土为人,女娲用土造出一个个人,用简单的工厂模式来实现。

在简单工厂模式中增加新的具体产品时是否符合*“开闭原则”*? 如果不符合, 原有系统需作出哪些修改?

女娲造人分析

factory:工厂角色

product:抽象产品角色

concreateProduct:具体产品角色

谁是工厂?女娲

谁是抽象产品?抽象的人

谁是具体产品?张三、李四

image-20230102072617870

某电视机厂专门为各知名电视机品牌代工生产各类电视机,当需要海尔电视机时只需要在调用该工厂的工厂方法时传入参数“haier",需要海信电视机时只需要传入参数"hisense",工厂可以根据传入的不同参数返回不同品牌的电视机,现使用简单工厂模式来模拟该电视机工厂的生产过程。

image-20230102072931261

image-20230102073025081

public interface TV { public void play(); } public class HisenseTV implements TV { public void play() { System.out.println("海信电视机播放中......"); } } public class HisenseTV implements TV { public void play() { System.out.println("海信电视机播放中......"); } } public class TVFactory { public static TV produceTV(String brand) throws Exception { if(brand.equalsIgnoreCase("Haier")) { System.out.println("电视机工厂生产海尔电视机!"); return new HaierTV(); } else if(brand.equalsIgnoreCase("Hisense")) { System.out.println("电视机工厂生产海信电视机!"); return new HisenseTV(); } else { throw new Exception("对不起,暂不能生产该品牌电视机!"); } } } public class Client { public static void main(String args[]) { try { TV tv; String brandName=XMLUtilTV.getBrandName(); tv=TVFactory.produceTV(brandName); tv.play(); } catch(Exception e) { System.out.println(e.getMessage()); } }

模式优缺点

优点

工厂类含有必要的判断逻辑,可以角色什么时候创建那一产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅消费产品;简单工厂模式通过这种做法实现了对责任的风格,他听过了专门的工厂类用于创建对象。客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,可以通过简单工厂模式可以减少使用者的记忆量通过引入配置文件,可以在不修改人呢和客户端代码的情况下更换和增加新的具体产品类 ,在一定程度上提高了系统的灵活性

缺点

由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,真个系统都要受到影响使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于负责,不利于系统的扩展和维护简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于集继承的等级结构。

模式使用环境

工厂类负责创建的对象比较少,由于创建的对象比较少,不会造成工厂方法中的业务逻辑太过复杂客户端只指导传入工厂类的参数,对于如何创建对象不关心,客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。

模式应用

(1) 在JDK类库中广泛使用了简单工厂模式,如工具类java.text.DateFormat,它用于格式化一个本地日期或者时间。

public finnal static DateFormat getDateInstance(); public finnal static DateFormat getDateInstance(int style); public finnal static DateFormat getDateInstance(int style, Locale locale);

(2)java加密技術

//获取不同加密算法的密钥生成器 KeyGenerator keyGen = keyGenerator,getInstance("DESde"); //创建密码器 Cipher cp = Cipher.getInstance("DESede");

简单工厂模式的简化

在有些情况下工厂类可以由抽象产品角色扮演,一个抽象产品类同时也是子类的工厂,就是说把静态工厂方法写到抽象产品类中。

image-20230102074244967

工厂方法模式

简单工厂模式的不足

在简单工厂模式中,只提供了一个工厂类,他知道没一个产品对象的创建西街,并决定何时实例化哪一个产品类简单工厂模式的最大缺点适当有新产品要加入到系统中时,必须修改工厂类,加入必要的处理逻辑,违背了开闭原则在简单工厂模式中,所有的产品都是由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的后合度高,严重影响了系统的灵活性和扩展性。

image-20230102074524516

按钮工厂类可以返回一个具体的按钮,圆形按钮,举行按钮,另行按钮等

在这个系统中,如果需要增加一个新类型的按钮,如圆形按钮,那么除了增加一个新的具体产品类之外,还需要修改工厂类的代码,这就使得这个设计再一定程度上违反了开闭原则。

image-20230102074645041

先定一个抽象的按钮工厂类,在定义具体的工厂类来生成圆形按钮、举行按钮、菱形按钮等,他们是现在抽象按钮工厂类中定义的方法

这种抽象化的结果使得这种结构可以在不修改具体工厂类的情况下引进新的产品

如果出现新的按钮类型,只需要为这种新类型的按钮创建一个具体的工厂类就可以获得该新按钮的实例,这一特点无疑使得工厂模式的具有超越简单工厂模式的优越性,更加符合开闭原则。

现在对该系统进行设计,不再设计一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成

工厂模式方法又称为工厂模式,工厂父类负责定义创建产品对对象的公共接口,而工厂子类负责生成具体的产品对象,这样做的目的就产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类

product:抽象产品ConcreateProduct:具体产品Factory:抽象工厂ConcreateFactory:具体工厂

image-20230102082603301

实例一:电视机工厂

将原有的工厂进行风格,为每种品牌的电视机提供一个子工厂个,海尔工厂专门负责生产海尔电视机,海信工厂专门负责生产你还是电视机,如果需要生产TCL电视机或创维电视机,只需要对应增加一个新的TCL工厂或创维工场即可,原有的欧工厂无须作任何修改,使得整个系统具有更加灵活些和可扩展性

image-20230102083052279

image-20230102083056451

public interface TvFactory{ public TV produceTV(); } public class HaierTVFactory implements TVFactory{ public TV produceTV(){ sout("海尔电视机工厂生产海尔电视机"); return new HaierTV(); } } public class Hisense TVFactory implements TVFactory{ public TV produceTv(){ sout("海信电视机工厂生产海信电视及"); return new HisenseTv(); } } public class client{ public static void main(String args[]){ try{ TV tv; TV factory; factory=(TVFactory)XMLUtil.getBean(); tv=factory.produceTv(); tv.play(); }catch(Exception e){ sout(e.getMessage()); } } }

宝马工厂制造宝马汽车,奔驰工厂制造奔驰汽车,使用工厂方法模拟该场景,绘制类图编写代码。

//图片读取器接口,抽象产品 interface Reader{ public void ReaderPic(); } //GIF读取器:具体产品 class GIFReader implements Reader{ public void ReaderPic(){ System.out.println("GIF读取"); } } //JPG读取器:具体产品 class JGPReader implements Reader{ public void ReaderPic(){ sout("JPGd读取"); } } //图片读取器工厂噶好难过接口:抽象工厂 interface ReaderFactory{ public Reader createReader(); } //GIF读取器工厂类:具体工厂 class GIFReaderFactory implements ReaderFactory{ public Reader createReader(){ Reader reader = new GiFReader(); return reader; } } //JPG读取器工厂接口:抽象工厂 class JPGReaderFactory implements ReaderFactory{ public Reader createReader(){ Reader reader = new JPGReader(); return reader; } } class Client{ public static void main(String arg[]){ ReaderFactory factory; Reader reader; factory = new GIFReaderFacotry(); reader = factory.craeteReader(); reader.ReaderPic(); } }

工厂方法模式的优点

在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户影藏了那种具体产品类将被实例化这一细节,用户只需要关系所需产品对应的工厂,无需关心创建西街,甚至无需知道具体产品类的类名。基于工厂角色和产品角色的多态性设计是工厂方法模式的关键,他能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部,工厂方法模式之所以又被称为杜台工厂模式,是因为所有的具体工厂类都具有统一抽象父类使用工厂方法模式的另一个优点是系统中加入新的产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户顿,也无须修改其他的具体工厂和具体产品,而只要添加一个具体的工厂和具体产品就可以了,这样系统的可扩展性也就变得非常好,完全符合开闭原则

工厂方法模式的缺点

再添加新产品时。需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统种类的个数成对增加。在一定程度上增加了系统的复杂度,有更多的累需要编译和运行,会给系统带来一些额外的开销

由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中只能怪均使用抽象层进行定义,增加了系统的抽象和理解难度,且在实时时可能需要DOM,反射等技术,增加了系统的实现难度。

模式适合环境

一个类不知道他所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,客户端需要知道具体产品的工厂类

一个类通过其子类来指定创建哪个对象:在工厂放模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象覆盖父类对象,从而使得系统更容易扩展

江川捡对象任务委托给多个工厂子类中的某一个,客户端在使用时可以无锡关心是哪一个工厂子类创建产品子类,需要时在动态制定,可以将具体工厂类的类名存储在配置文件或数据库中。

模式应用

java.util.Collection接口的iterator()方法

image-20230102093309117

(2) Java消息服务JMS(Java Messaging Service) :

//使用上下文和JNDI得到连接工厂的引用,ctx是上下文Context类型的对象 QueueConnectionFactory qConnFact=(QueueConnectionFactory)ctx.lookup('cdIndi'); //使用连接工厂创建一个链接 QueueConnection qConn=qConnFact.createQueueConnection(); //使用链接创建一个会话 QueueSession qSess =

(3) JDBC中的工厂方法

Connection conn = DriverManager.getConnection("jdbc.sxx"xxx); Satatement statement = conn.createStatement(); ResultSet rs = statement.executeQuery("select * from UserInfo");

模式扩展

使用多个工厂方法:在抽象工厂角色中可以定义多个工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些放阿飞可以包含不同的业务逻辑,以满足对不同的产品对象需求产品对象的重复使用:工厂对象将已经创建过的产品保存到一个集合(如数组、list等)中,然后根据客户对产品的请求,堆积和精心查询。如果有满足要求的产品对象,就直接将该产品返回客户端,如果集合中没有这样的产品对象,那么久创建一个新的满足要求的产品对象,然后将这个对象在增加到集合中,再返回给客户端多态性的丧失和模式的退化:如果工厂仅仅返回一个具体产品对象,便违背了工厂方法的用意,发生退化,此时就不再是工厂方法发模式了,一般来说。工厂对象应当有一个抽象的父类型,如果弓藏等级结构中只有一个具体工厂类的话,抽象工厂就可以省略,也将发生了退化,当只有一个具体产品,,再具体工厂中可以创建所有的产品对象,并且工厂方法设计伪静态方法时,工厂方法模式就退化成简单工厂。

工厂方法总结

工厂方法模式又称为工厂模式,属于类创建型模式在工厂方法模式中,工厂父类负责定义产品对象的公共接口,而工厂子类负责生成具体的产品对象这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来来确定究竟应该实例化哪一个具体产品类

工厂方法模式包含四个角色

抽象产品是所创建对象的超类型,即产品对象的共同父类或接口具体产品实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,他们之间一一对应抽象工厂声明了工厂方法,用于返回一个产品,他是工厂方法模式的狠心,任何在模式中创建找对象的工厂类都必须实现该机可偶具体工厂是抽象工厂类的子类,实现了抽象工厂产品中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例 抽象工厂模式

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由工厂方法模式中的每个工厂只伸长一类产品,可能会导致系统中存大量的工厂类,势必会增加系统的开销。

sunny软件公司与开发一套界面皮肤裤,用户在使用时可以通过菜单来选择皮肤,不同的匹夫将提供视觉效果不同的按钮、文本框、组合框等界面元素,其结构示意图如图所示

image-20230102095007450

该皮肤裤具备良好的灵活性和可扩展性,用户可以自由选择不同的皮肤,开发人员可以在不修改既有代码的基础上增加新的皮肤

抽象工厂

image-20230102095251003

在图中,提供了大量工厂来创建具体的界面组件,可以通过配置文件更换具体界面组件从而改变界面风格

当需要增加新的皮肤时,虽然不要修改现有代码,但是需要针对每一个新增具体组件都要增加一个具体工厂由于同一种风格的具体界面组件通常要一起显示,因此需要为每个组件都选择一个具体工厂,用户在使用时必须逐个进行设置

如何减少系统中类的个数并保证客户端每次始终只使用某一种风格的具体界面组件?

模式动机 产品等级结构:茶农等级结构即产品的继承结构产品族:在抽象工厂模式中,产品族是指由同一个工厂生产,唯一不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机,海尔电冰箱,海尔电视机位于产品等级结构中,海尔电冰箱位于电冰箱产品结构中。

image-20230102095752935

抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中产品对象的创建,抽象工厂模式比工厂方法模式更为简单,有效率

每一个具体工厂可以生产属于一个产品族的所有产品,例如生产颜色相同的正方形、圆形和椭圆形,所生产的产品又位于不同的产品等级结构中,如果使用工厂方法模式,图4所示结构需要提供15个具体工厂,而使用抽象工厂则需要提供五个具体工厂

image-20230102100032063

模式定义

抽象工厂模式,提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类,抽象工厂模式又称为KIT模式,属于对象创建型模式

抽象工厂包含如下角色:

1.AbstractFactory:抽象工厂

2.ConcreateFactory:具体工厂

3.AbstractProuct:抽象产品

4.Product:具体产品

image-20230102101203262

image-20230102101602340

抽象工厂

image-20230102101937570

抽象工厂类的典型代码如下

public abstract class AbstractFactory { public abstract AbstractPrductA createProductA(); public abstract AbstractProductB createProductB(); } 具体工厂类的代码 public class ConcreateFactory1 extends AbstractFactory{ public AbstractProductA createProductA(){ return new ConcreateProductA1(); } public AbsratctProductB createProductB(){ return new ConcreateProductB1(); } }

Sunny公司开发人员使用抽象工模式来重构界面皮肤裤的设计,其结构图所示

image-20230102102316179

SkinFactory接口充当抽象工厂,其子类SpringSkinFactory和SunmmerSkinFactory充当具体工厂,接口Button,TextField和ComBBObOX充当抽象产品,其子类SpringButton,SpringTextField\Sp\SpringCombobox和SummerTextField、SUMMERCOMBOBOX充当具体产品

//按钮接口 抽象产品 interface Button{ public void display(); } //spring按钮:具体产品 class SpringButton implements Button { public void display(){ sout("显示浅绿色按钮"); } } //summer按钮类:具体产品 class SummerButton implements Button { public void display(){ sout("显示浅绿色按钮"); } } //文本框接口 //文本框接口: 抽象产品 interface TextField { public void display(); } //Spring文本框类: 具体产品 class SpringTextField implements TextField { public void display() { System.out.println("显示绿色边框文本框。 "); } } //Summer文本框类: 具体产品 class SummerTextField implements TextField { public void display() { System.out.println("显示蓝色边框文本框。 "); } } //组合框接口: 抽象产品 interface ComboBox{ public void display(); } //spring组合框类:具体产品 class SpringCoboBox implements ComboBox { public void display(){ sout("显示绿色边框组合框") } } //summer组合框类:具体产品 class SummerComboBox implements ComboBox{ public void display(){ sout("显示蓝色边框组合") } } //界面皮肤工厂借口:抽线工厂 interface SkinFactory{ public Button createButton(); public TextField create TextField(); public ComboBox createComboBox(); } //Spring皮肤工厂 class SpringSkinFactory implements SkinFactory{ public Buttton createButton(){ return new SpringButton(); } public TextFieled createTextField(){ return new SpringTextField(); } public ComboBox createComboBox(){ return new SpringComboBox(); } } //Summer皮肤工厂:具体工厂 class SummerSkinFactory implements SkinFactory { public Button createButton() { return new SummerButton(); } public TextField createTextField() { return new SummerTextField(); } public ComboBox createComboBox() { return new SummerComboBox(); } }

image-20230102110308796

class Client{ public static void main(String agrs[]){ //使用抽象层定义 SkinFactory factory; Button bt; TextFieled tf; ComboBox cb; factory=(SkinFactory)XMLUtil.getBean(); bt = factory.createButton(); tf = factory.createTextField(); cb = factory.createComboBox(); bt.display(); tf.display(); cb.display(); } }

练习:电器工厂

一个电器工厂可以生产多种类型的电器,如海尔工厂可以生产海尔电视机、海尔空调等,TCL工厂可以生产tcl电视机,tcl空调等,相同品牌的电器构成一个产品族,而相同类型的电器构成了一个产品等级结构,先使用抽线工厂模拟该场景。

image-20230102110632648

image-20230102111101823

练习:数据库操作工厂

某系统为了改进数据库操作的性能,自定义数据库连接对象Connecetion和语句对象Statement,可针对不同类型的数据库提供不同的连接对象he语句对象,如提供Orace或sql server专用链接类和语句类,而且用户可以通过配置文件等方式根据实际需要动态更换数据库,使用抽象工厂模式设计该系统

image-20230102111721902

抽象工厂模式的优点

抽线工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个局提供就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统发的行为,另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只是用同一个产品组的对象,这对一些需要根据当前环境来决定请行为的软件系统来说,是一种非常实用的设计模式增加新的具体共产和产品族和方便,无需修改已有系统,符合开闭原则

抽象工厂模式的缺点

再添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽线工厂角色规定了所有可能被创建的产品集合,要支持心中类的产品就意味着要对该接口进行扩展,而浙江涉及到对抽象工厂角色及IQis偶有子类的修改,显然会带来较大的不便开闭原则的倾斜性(增加新的工厂和产品族很容易,增加新的产品等级结构麻烦)

模式适用环境

一个系统不应当依赖与产品类如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的系统中有多于一个的产品族,而每次只使用其中某一产品族属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来系统提供一个产品类的库,所有的产品以同样的即可欧出现,从而使客户端不依赖与具体的实现

模式应用

(1) Java SE AWT(抽象窗口工具包)

在java语言中的AWT(抽象窗口工具包)中就使用了抽象工厂模式,他使用抽象工厂模式来实现在不同的操作系统应用程序呈现与所正在操作系统一致的外观界面

(2)在很多软件系统中需要更换界面主题,要求界面中的按钮、文本框、背景色等一起发生改变时,可以使用抽象工厂模式来进行设计

(3)微软演示项目PETshop提供了对多种数据库的支持

image-20230102112839862

模式扩展

开闭原则的倾斜性

开闭原则要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的,对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面 增加产品族,对于增加新的产品族,工厂方法模式很好地支持了开闭原则对于新增加的产品族,只需要对应增加一个新的具体工厂计科,对已有代码毋须做任何修改增加新的产品登机结构:对新增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类都需要增加生产新产品的方法,不能很好地支持开闭原则抽象工厂模式的这种性质被称为开闭原则的倾斜性,臭相公模式以一种倾斜的方式支持增加新的产品,他为新产品族的增加提供方便,但不能为新的产品等级结构的增加提供这样的方便

工厂模式的退化

当臭相公模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创将对象的工厂方法设计为静态方法,工厂方法模式退化成简单工厂模式

原型模式

西游记孙悟空可以用猴毛根据自己的形象,复制(又称克隆或拷贝)处很多跟自己长得一模一样的身外身来

image-20230102113501465

在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法来建造更多同类型的对象,这就是原型模式的意图所在

Sunny软件公司使用自行开发的一套系统进行日常工作办理,但在使用过程中,越来越多的人对工作周报的创建和编写模块产生了抱怨追其原因,由于某些岗位每周工作存在重复性,工作周报内容都大同小异,这些周报制有一些小地方存在差异,但是线性系统每周默认创建的周报都是空白报表,用户只能通过重新输入或不断复制黏贴来填写重复的周报内容,,极大降低了工作效率,浪费宝贵的时间

image-20230102113748061

公司的开发人员对问题进行仔细研究,决定按照如下思路对工作周报模块进行重新设计和实现

除了允许用户创建新周报外,还允许用户将创建好的周报保存为模板用户在再次创建周报时,可以创建全新的周报,还可以选择合适的模板复制生成一份相同的周报,然后对新生成的周报根据实际情况进行修改,产生新的周报

模式定义

原型模式是一种对象创建型模式,用原型实例制定创建对象的种类,并且通过复制这些原型创建新的对象。原型模式允许一个对象在创建另外一个可以定制的对象,无需知道任何创建的细节原型模式的基本工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝原型来实现创建过程

image-20230102114518078

原型模式包含如下角色

Prototype:抽象原型类ConcreateProtype:具体原型类Client:客户类

模式分析

在圆形模式结构中定义了一个抽象原型类,所有的java类都继承自java.lang.object,。而object类提供一个==clone()==方法,可以将一个java对象复制一份。因此在java中可以直接使用objecty提供的clone()方法来实现对象的克隆,java语言中的原型模式实现很简单能够实现克隆的java类必须实现一个标识接口Cloneable,表是这个java类支持复制,如果一个类没有实现这个接口但是调用了clone()方法,java编译器将抛出一个CloneNotSuppoRTEDeXCEPTION异常

实例代码

public class PrototypeDemo implements Cloneable { public Object clone() { Obeject object = null; try{ object = super.clone(); }catch(CloneNotSupportedException exception){ sout("not support cloneable") } return object; } }

java语言提供的clone()方法将对象复制了一份并返回给调用者,一般而言。clone()方法满足

对任何的对象x,都有x.clone()!=x,即克隆对象与原对象不是同一个对象对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样如果对象x的equlas()方法定义恰当,那么x.clone().equals(x)应该成立

sunny公司开发人员决定使用原型模式来实现工作周报的快速创建,快速创建工作周报结构图如图所示

image-20230102121714461

weeklyLog充当具体原型类,Object类充当抽象原型类,clone()方法为原型方法

//工作周报WeeklyLog:具体原型类,考虑到代码的可读性和易理解性,只列出部分与模式相关的核心代码 class WeeklyLog implements Cloneable { private String name; private String date; private String content; public WeeklyLog clone() { Object obj = nul; try{ obj = super.clone(); return (WeeklyLog)obj; } catch(CloneNotSupportedException e){ sout("不支持复制"); return null; } } } class Client { psvm{ WeeklyLog log_previous = new WeeklyLog();//创建原型对象 log_previous.setName("张无忌"); log_previous.setDate("第12周"); log_previous.setContent("这周工作很忙, 每天加班! "); WeeklyLog log_new = log_previous.clone();//调用克隆方法创建克隆对象 log_new.setDate("第13周"); System.out.println("****周报****"); System.out.println("周次: " + log_new.getDate()); System.out.println("姓名: " + log_new.getName()); System.out.println("内容: " + log_new.getContent()); } }

image-20230102122347866

带附件的周报

通过引入原型模式,Sunny软件公司OA系统支持工作周报的快速克隆,极大提高了工作周报的编写效率,收到员工的一致好评

但有员工又发现一个问题,有些工作周报带有附件,例如经历助理“小龙女

的周报通常附有本周项目进展报告汇报表,本周客户反馈信息汇总表等,如果使用上述原型模式来复制周报,周报虽然可以复制,但是周报附件并不能复制,这是由于什么原因导致的呢?

如何才能实现周报和附件的同时复制呢?

模式分析

通常情况下,一个类包含一些成员对象,在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为那两种形式:深克隆和浅克隆

image-20230102122833623

浅克隆

image-20230102123103358

在浅克隆中,如果远行对象的成员变量是值类型,将复制一份给克隆对象

如果原型对象的成员变量是引用类型,则引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址简单来说,在浅克隆中,当对象被复制时复制它本身和其中包含的值类型的成员变量而引用类型的成员对象并没有复制

在java语言中,通过覆盖Object类的clone()方法可以实现浅克隆,为了让大家更好理解浅克隆和深克隆的区别,我们首先使用浅克隆来实现工作周报和附件类的复制

image-20230102123339756

class Attachment { private String name;//附件名 public void setName(String name) { this.name=name; } public String getName() { return this.name; } public void download() { System.out.println("下载附件, 文件名为" + name); } } //工作周报WeeklyLog class WeeklyLog implements Cloneable { //为了简化设计和实现,假设一份工作周报中只有一个附件对象,实际情况中可以包含多个附件,可以通过List等集合对象来实现 private Attachment attachment; private String name; private String date; private String content; public void setAttachment(Attachment attachment) {this.attachment = attachment;} public void setName(String name) {this.name = name;} public void setDate(String date) {this.date = date;} public void setContent(String content) {this.content = content;} public Attachment getAttachment() {return (this.attachment); public String getName() {return (this.name);} public String getDate() {return (this.date);} public String getContent() {return (this.content);} //使用clone()方法实现浅克隆 public WeeklyLog clone() { Obejct obj = null; try { obj = super.clone(); return (WeeklyLog)obj; } catch(CloneNotSupportedException e) { sout("不支持复制"); return null; } } } class Client { psvm{ WeeklyLog log_previous,log_new; log_previous = new WeeklyLog(); Attachment attachment = new Attachment();//创建附件对象 log_previous.setAttachment(attachment);//将附件添加到周报中 log_new = log_previous.clone();//调用克隆方法创建对象 //比较周报 sout("周报是否相同?"+(log_previous==log_new)) //比较附件 sout("附件是否相同?"+loh_previous.getAttachment()==log_new.getAttachment()) } }

编译并运行程序,输出结果如下

image-20230102124312422

由于使用的是浅克隆技术,因此工作周报对象复制成功,通过==比较原型对象和克隆对象的内存地址时输出false,但是比较附件对象的内存地址时输出true,说明他们在内存中是同一个对象

深克隆

image-20230102124631339

在深克隆中,无论原型对象成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的 所有成员对象也将复制

image-20230102124809882

在java语言中,如果要实现深克隆,可以通过序列化(Serialization)等方式来实现,序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象任然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象棋类必须实现Serializable接口,否则无法实现序列化操作

image-20230102131129531

import java.io.*; //附件类 class Attachment implements Serializable { private String name; public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void download() { System.out.println("下载附件, 文件名为" + name); } } class WeeklyLog implements Serializable { private Attachment attachment; private String name; private String date; private String content; } //使用序列化技术实现深克隆 public WeeklyLog deepClone() throws IOEeception,ClassNotFoundException,OptionalDataException { //将对象写入流中 ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); oos.writeObjects(this); //讲对象从流中取出 Byte ArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (WeeklyLog)ois.readObject(); } class Client { public static void main(String args[]) { WeeklyLog log_previous,log_new =null; log_previous = new WeeklyLog();//创建原型对象 Attachment attachment = new Attachment();//将附件添加到周报中 try{ log_new=log_previous.deepClone();//调用深克隆方法创建克隆对象 }catch(Exception e) { sout("克隆失败") } sout("周报是否相同?"+log_previous==log_new) sout("附件是否相同?"+log_previous.getAttachment()==log_new.getAttachment()) } }

image-20230102131926889

练习:邮件复制(浅克隆)

由于邮件对象包含的内容较多(如发送者,接受者,标题,内容,日期。附件等)某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可,使用原型模式设计该系统。在本示例中使用浅克隆实现邮件复制,即复制邮件(Email)的而同时不复制附件(Attachment)

image-20230102132320924

练习:邮件复制(深克隆)

image-20230102132442032

原型模式的优点

当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率可以动态总价或减少产品类原型模式提供了简化的创建结构可实用深克隆的方式保存对象的状态

缺点

需要为每一个类配备一个克隆方法,而且在个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了开闭原则在实现深克隆时需要编写较为复杂的代码

模式使用环境

创建新对象成本较大,新的对象可以通过原型模型对已有对象进行复制来获得如果是相似对象,则可以对其属性稍作修改如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不打的时候,也可以使用原型模式配合备忘录模式来应用,相反,,如果对象的状态变化很大或者对象占用的内存很大,那么采用状态模式会比原型模式更好需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个的组合状态,通过复制原型对象得到新实例可能比使用构造函数构造一个新实例更加方便

模式应用

原型模式应用在很多软件中,如果每次创建一个对象要花大量时间,原型模式就是最好的解决方案。很多软件提供的复制黏贴曹组就是对原型模式的应用,复制得到的对象与原型对象时两个类型相同但内存地址不同的对象,通过原型模式可以大大提高对象的创建效率在Struts2中为了保证线程的安全性,Action独享的创建使用了原型模式,访问一个已经存在的Action对象时通过克隆的方式创建出一个新的对象,从而保证其中定义的变量无须进行加锁实现同步,每一个Aciton中都有自己的成员变量,避免Strust1因使用单例模式而导致的并发和同步问题在spring中,用户也可以采用原型模式来创建新的bean实例,从而实现每次获得是通过克隆生成的新实例,对其修改时对原有实例对象不造成任何影响。

模式扩展

带有原型管理器的原型模式

image-20230102133755867

相似对象的复制

很多情况下,复制所得到的对象与原型对象并不是完全相同的,他们的某些属性值存在异同,通过原型模式获得相同对象后可以在对其属性进行修改,从而获取所需对象如多个学生对象的信息区别在于性别、姓名、和年龄,而专业学院学校等信息都相同,为了简化创建过程,可以通过原型模式来实现相似对象的复制

单例模式

模式动机

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务,一个系统只能有一个窗口管理器或文件系统,一个系统只能有一个计时工具或ID生成器

image-20230102134020135

如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象

一个更好的解决方法是让类的自身负责保存他的唯一实例,这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法,这就是单列模式的模式动机

模式定义

单例模式:单例模式缺保姆偶一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类被称为单例类,他提供全局访问的方法

单列模式的要点有三个:一是某个类只能有一个实例,二是他必须自行创建这个实例,三是他必须自行向整个系统提供这个实例,单例模式是一种对象创建型模式,单例模式又名单件模式或单态模式

image-20230102134614520

模式分析

单例模式的目的是保证一个类仅有一个实例,并提供一个访问他的全局访问点,单例模式包含的角色只有一个,就是单例类-Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化他,除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建

单例模式的实现代码如下

public class Singleton { private static Singleton insntance =null;//静态私有成员变量 //私有构造函数 private Singleton(){ } //静态共有工厂方法,返回唯一实例 public static Singleton getInstance() { if(instance ==null) { instance = new Singleton(); return instance; } } }

在单例模式的实现过程中

单例模式的构造函数为私有提供一个自身的静态私有成员变量提供一个共有的静态工厂方法

实例一:身份证号码

在现实生活中,居民身份证号码具有唯一性,同一个人不允许有多个身份证号码,第一次申请身份证时将给居民分配一个身份证号码,如果之后因为遗失等原因补办时,还是使用原来的身份证号码,不会产生新的号码,先使用单例模式模拟该场景

image-20230102135937037

public class IdentityCardNo { private static IdentityCardNo instance = null; private String no; private IdentityCardNo(){} public static IdentityCardNo getInstance() { if(instance==null) { sout("第一次办理身份证,分配新号码"); instance = new IdentytyCardNo(); instance.setIdentityCardNo("No4000000"); }else { sout("重复办理身份证,获取旧号码"); } return instance; } private void setIdentityCardNo(String no){ this.no = no; } public String getIdentityCardNo() { return this.no; } } class Client { psvm { IdenittyCardNo no1,no2; no1 = IdentityCardNo.getInstance(); no2 = IndentiCardNo.getInstance(); sout("身份证号码是否一致?"+no1==no2); String str1,str2; str1 = no1.getIdenttiyCardNo(); str2 = no2.getIdentityCardNo(); sout("第一次号码"+str1); sout("第二次好吗"+str2); sout("内容是否相等"+str1.equalsIgnoreCase(str2)); sout("是否为相同对象"+str1==str2) } }

Sunny软件公司承接了一个服务器负载均衡load balance软件的开发工作,该软件运行一台负载均衡服务器,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高学习通的整体处理能力,缩短相应时间

由于集群中的服务器需要动态删减,且客户端请求需要同一分发,因此需要确保负载均衡的唯一性,只能有一个负载均衡器来负责服务器的管理和请求的分发否则会带来服务器状态的不一致以及请求分配冲突等问题,如何确保负载均衡器的唯一性是该软件成功的关键

image-20230102144505128

sunny公司开发人员通过分析和权衡,决定使用单例模式来设计该负载均衡器,结构图如图所示

image-20230102144540627

将负载均衡器LoadBalance设计为单例类,其中包含一个存储服务器信息的集合serverlist,每次在Serverist中随机选择一台服务器来响应客户端的请求

//负载均衡器LoadBalance单例类,真实环境下该类将非常复杂,包括大量初始化的工作和业务方法,考虑到代码的可读性和易理解性,只列出与模式相关的核心代码 class LoadBalancer { //私有静态成员变量,粗糙农户唯一实例 private static LoadBalancer instance = null; //服务器集合 private List serverlist = null; //私有构造函数 private LoadBalancer() { serveList = new ArrayList(); } //共有静态成员方法 public static LoadBalancer getLoadBalancer() { if(instance == null) instance = new LoadBalancer(); return instance; } //增加服务器 public void addServer(String server) {serverList.add(server);} //删除服务器 public void removeServer(String server) {serverList.remove(server);} //使用Random类随机获取服务器public String getServer() {Random random = new Random(); int i = random.nextInt(serverList.size());return (String)serverList.get(i);}} } class Client { public static void main(String args[]) { //创建四个LoadBalancer对象 LoadBalancer balancer1,balancer2,balancer3,balancer4; balancer1 = LoadBalancer.getLoadBalancer();balancer2 = LoadBalancer.getLoadBalancer(); balancer3 = LoadBalancer.getLoadBalancer();balancer4 = LoadBalancer.getLoadBalancer //判断服务器负载均衡器是否相同 if (balancer1 == balancer2 && balancer2 == balancer3 && balancer3 == balancer4) {System.out.println("服务器负载均衡器具有唯一性! ");} //增加服务器 balancer1.addServer("Server1"); balancer1.addServer("Server2"); balancer1.addServer("Server3"); balancer1.addServer("Server 4"); //模拟客户端请求的分发 for(int i = 0;i private static PrintSpoolerSingleton instance = null; private PrintSpoolerSingleton(){} public static PrintSpoolerSingleton getInstance() throws PrintSpoolerException{ if(instance == null) { sout("创建打印池"); instance = new PrintSpoolerSingleton(); } else { throw new PrintSpoolerException("打印池正在工作中"); } return instance; } public void manageJobs { sout("管理打印任务!"); } } public class PrintSpoolerException extends Exception { public printSpoolerException(String message) { super(message); } } public class Client { psvm{ PrintSpoolerSingleton ps1,ps2; try { ps1=PrintSpoolerSingleton.getInstance(); ps1.manageJobs(); } catch(PrintSpoolerException e) { System.out.println(e.getMessage()); } System.out.println("--------------------------"); try { ps2=PrintSpoolerSingleton.getInstance(); ps2.manageJobs(); } catch(PrintSpoolerException e) { System.out.println(e.getMessage()); } } } }

模式优点

提供了对唯一实例的受控访问,因为单例类封装了他的唯一实例,所以他可以严格控制客户怎样以及何时访问他,并为设计及开发团队提供了共享的概念由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能允许可变数目的实例,我们可以基于单例模式,使用与单例控制相似的方法来获得指定个数的对象实例

模式缺点

由于单利模式中没有抽象层,因此单例列的扩展有很大困难单例类的职责过重,在一定程度上违背了单一职责原则,因为单例类即充当了工厂角色,提供了工厂方法,同时又充当了产品角色, 包含一些业务方法,将产品的创建和产品的本身的功能融合打到一起滥用单例将带来一些负面问题,如为了节省省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。现在很多面对象语言的运行环境都提供了自动垃圾回收的技术,因此如果实例化的对象长时间不被利用,系统会认为他是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,,这将导致对象状态的丢失

模式使用环境

系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他路径访问该实例在一个系统中要求一个类只有一个实例时才应当适用单例模式。反过来,如果一个类可以有几个实例共存。就需要对单例模式进行改进,使之成为多例模式

模式应用

java.lang.Runtime

public class Runtime { private static Runtime currentRuntim=new Runtime(); public static Runtime getRuntime(){ return currentRuntime; } private Runtime(){ } }

一个具有自动编号主键的表可以有多个用户同时使用,但数据库只能有一个地方分配下一个主键编号,否则会出现主键重复,因此该主键编号生成器必须具备唯一性,可以通过单例模式来实现

image-20230102151225972

默认情况下,Spring会通过单例模式创建bean实例

模式扩展

饿汉式单例

image-20230102151331779

if(instance==null) instance= new LazySingleton(); return instance;

饿汉式单例与懒汉式单例类比较

饿汉式单例类在自己被加载时就将自己实例化,但从资源利用效率角度来讲,这个笔懒汉式单例类稍差些。从速度和反应时间角度来看,则必懒汉式单例类稍好些饿汉式单例类在实例化时,必须处理好在多个线程同时受此应用此类时的访问限制问题。特别是当单例类作为资源控制器,在实例化时必然设计资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次应用此类的几率变得较大,需要通过同步机制进行控制


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3