推荐阅读

第一章:程序设计原则—单一职责、接口隔离、依赖倒置、里式替换

第二章:程序设计原则—开闭原则、迪米特法则、合成复用原则

推荐书籍:

喜欢的请不要忘记三连加关注哦!更多优质技术文章和经验分享陆续推出。有想了解的方面也支持评论区点播!

一、设计模式简单介绍

1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书,在本教程中收录了 23 个设计模式,这是设计模式领域里程碑的事件,导致了软件设计模式的突破。这 4 位作者在软件开发领域里也以他们的“四人组”(Gang of Four,GoF)匿名著称。

1.1、什么是设计模式

软件工程中的设计模式(design pattern)是对软件设计中普遍存在(反复出现)的的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛等人在1990年从建筑设计领域引入到计算机科学中的

设计模式考虑的是一个软件的结构上怎么更合理,如何提高稳定性、复用性、扩展性上考虑,而不仅仅是功能实现上。如果站在一个功能实现上可能用不到设计模式。但是如果站在软件体系、结构的角度上来考虑比较有意义。

说白了就是写代码的套路,前人将一些常见的场景进行优化,变成一个个的编码套路,模板,我们按照这个套路来写代码的话,代码的可读性,维护性,扩展性等就会更强。这就是`设计模式`

1.2、设计模式为什么重要1.3、设计模式的目的

在程序编写过程中,程序员面临着耦合性、内聚性、维护性、扩展性、重用性、稳定性等方面带来的挑战。设计模式就是来解决软件开发中的以上问题。设计模式包含了面向对象的精髓,懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要

1.4、设计模式分类1.4.1、创建型模式

这些设计模式用于描述“`怎样创建对象`”,它的主要特点是“将对象的创建与使用分离”:

1.4.2、结构型模式

这些设计模式关注类和对象的组合,用于描述如何将类或对象按某种布局组成更大的结构:

1.4.3、行为型模式

用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责:

提示:将以3章分别讲述3类设计模式

二、单例模式

单例模式是比较常见的一种设计模式,面试的时候有时候会让你写一下单例,我就遇到过让我写单例、工厂、观察者模式的面试。

单例(Singleton)指一个类只能有一个实例,且该类能自行创建这个实例。例如Windows中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成的内存浪费,或出现多个窗口显示内容不一致的错误。

单例模式在现实生活中应用也很广泛,例如公司CEO、部门经理等都属于单例模型,如果领导太多,意见不符,对公司的发展会造成很不好的影响。J2EE标准中的ServletContext 和 ServletContextConfig、Spring框架中的ApplicationContext、数据库中的连接池都是单例,这里可以细品一下为什么设计成单例的?

单例模式有3个特点:

2.1、实现方式

通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

单例模式结构主要包含两个角色:

单例大概有8种实现方式,其中重要的推荐使用的会单独标记出来,请注意文字提示哦

2.1.1、饿汉式(静态常量)

// 测试类 public class SingletonTest1 { public static void main(String[] args) { // 获取单例对象 Singletion01 instance01 = Singletion01.getInstance(); // 获取单例对象 Singletion01 instance02 = Singletion01.getInstance(); // 比较两个对象引用地址是否相同 System.out.println(instance01 == instance02); } } // 单例类 class Singletion01 { // 实例化单例对象 private static final Singletion01 instance = new Singletion01(); // 私有化构造方法 private Singletion01() { } // 对外提供方法返回单例对象 public static Singletion01 getInstance() { return instance; } }

优点

缺点

这种单例模式可用,可能造成内存浪费

2.1.2、饿汉式(静态代码块)

// 测试类 public class SingletonTest2 { public static void main(String[] args) { // 获取单例对象 Singleton instance01 = Singleton.getInstance(); // 获取单例对象 Singleton instance02 = Singleton.getInstance(); // 比较两个对象引用地址是否相同 System.out.println(instance01 == instance02); } } // 单例类 class Singleton { // 声明静态变量 public static Singleton singleton; // 在静态代码块中创建对象,并赋值给静态变量,我们知道类只能被加载一次,可保证单例 static { singleton = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return singleton; } }

它的优缺点等和静态常量是一样的

2.1.3、懒汉式(线程不安全)

// 测试类 public class SingletonTest03 { public static void main(String[] args) { // 获取单例对象 Singleton singleton1 = Singleton.getInstance(); // 获取单例对象 Singleton singleton2 = Singleton.getInstance(); // 比较两个对象引用地址是否相同 System.out.println(singleton1 == singleton2); } } // 单例类 class Singleton { // 声明静态变量 private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { // 判断是否实例化 if(singleton == null) { singleton = new Singleton(); } return singleton; } }

优点

缺点

在实际开发中,不要使用这种方式

2.1.4、懒汉式(同步方法)

// 测试类 public class SingletonTest4 { public static void main(String[] args) { // 获取单例对象 Singleton singleton1 = Singleton.getInstance(); // 获取单例对象 Singleton singleton2 = Singleton.getInstance(); // 比较两个对象引用地址是否相同 System.out.println(singleton1 == singleton2); } } // 单例类 class Singleton { private static Singleton singleton; private Singleton() {} // 同步方法加锁,解决多线程安全问题 public static synchronized Singleton getInstance() { if(singleton == null) { singleton = new Singleton(); } return singleton; } }

优点

缺点

在实际开发中,不推荐使用

2.1.5、同步代码块

// 测试类 public class SingletonTest5 { public static void main(String[] args) { // 获取单例对象 Singleton singleton1 = Singleton.getInstance(); // 获取单例对象 Singleton singleton2 = Singleton.getInstance(); // 比较两个对象引用地址是否相同 System.out.println(singleton1 == singleton2); } } // 单例类 class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { // 判断是否实例化过 if(singleton == null) { // 同步代码块 synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }

这种方式的本意是解决第四种方法,效率低的问题,但是不能保证线程安全,一样的在判断空完了之后没来的获取锁,另一个线程进来,也是可以再创建对象的,其实就是加锁的地方不对。所以开发中不要使用

2.1.6、双重检查

// 测试类 public class SingletonTest6 { public static void main(String[] args) { // 获取单例对象 Singleton singleton1 = Singleton.getInstance(); // 获取单例对象 Singleton singleton2 = Singleton.getInstance(); // 比较两个对象引用地址是否相同 System.out.println(singleton1 == singleton2); } } // 单例类 class Singleton { private static volatile Singleton singleton; private Singleton() {} public static Singleton getInstance() { // 判断是否创建实例 if(singleton == null) { // 同步锁,解决多线程安全问题 synchronized (Singleton.class) { // 再次判空,保障不被重复创建 if(singleton == null) { singleton = new Singleton(); } } } return singleton; } }

优点

在开发中,推荐使用这种单例设计模式

2.1.7、静态内部类

public class SingletonTest7 { public static void main(String[] args) { // 获取单例对象 Singleton singleton1 = Singleton.getInstance(); // 获取单例对象 Singleton singleton2 = Singleton.getInstance(); // 比较两个对象引用地址是否相同 System.out.println(singleton1 == singleton2); } } // 单例类 class Singleton { private static volatile Singleton singleton; private Singleton() {} // 静态内部类 private static class SingletonInstance{ // 创建外部类对象 private static final Singleton INSTANCE = new Singleton(); } // 返回外部类对象 public static synchronized Singleton getInstance() { return SingletonInstance.INSTANCE; } }

优点

推荐使用

2.1.8、枚举

该方式是 《Effective Java》一书的作者 《Joshua Bloch》提出的,大佬发现市面上很多人都子啊研究单例咋整,咋优雅,咋安全,咋性能好。这哥们觉得好low啊,搞得好复杂,利用JDK1.5之后推出的枚举类,一出手就是 最优解决方案

public class SingletonTest8 { public static void main(String[] args) { Singleton instance1 = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance1 == instance2); } } enum Singleton { INSTANCE; }

优点

推荐使用

2.2、单例的优缺点

优点

缺点

2.3、单例应用场景

对于Java 来说,单例是保证一个JVM中只存在单一实例,主要应用场景:

2.4、最佳实践总结

推荐使用枚举、双重检查和饿汉式方式,强烈建议使用枚举。

单元素的枚举类型已经成为实现Singleton的最佳方法

-- 出自 《effective java》                 

三、简单工厂模式

该设计模式并不包含在GOF提出的23种设计模式之中,但是也是值得拿来说一说的。

工厂模式定义一个常见产品对象的工厂接口,将产品对象的实际 创建工作 推迟到具体子工厂类中,这满足创建型模式中所要求的“创建与使用相分离”的特点。

按照实际业务场景划分,工厂模式分为 `简单工厂模式`, `工厂方法模式`, `抽象工厂模式`。我们把被创建的对象称为"产品",把创建产品的对象称为"工厂",`如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”`。

在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作`静态工厂方法模式`(Static Factory Method Pattern)。

简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品,属于创建型设计模式。

简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。

3.1、代码实现

我们通过原始方式和工厂模式方式来实现购买披萨的场景。我们想要通过一个商店来订购不同种类的披萨,比如奶酪披萨、希腊披萨等

3.1.1、原始方式

// 1、将披萨类做成一个抽象类 public abstract class Pizza { protected String name; public abstract void prepare(); public void bake() { System.out.println(name "baking;"); } public void cut() { System.out.println(name "cut;"); } public void box() { System.out.println(name "box;"); } public void setName(String name) { this.name = name; } } // 2、奶酪披萨 public class CheesePizza extends Pizza{ @Override public void prepare() { System.out.println("给奶酪披萨准备原材料"); } } // 3、希腊披萨 public class GreekPizza extends Pizza{ @Override public void prepare() { System.out.println("给希腊披萨准备原材料"); } } // 4、披萨商店 public class OrderPizza { private static Scanner scanner = new Scanner(System.in); /** * 订购披萨 */ public void getPizza() { Pizza pizza = null; // 订购披萨类型 while (true) { System.out.println("请输入披萨种类:"); String pizzaType = scanner.next(); if("cheese".equals(pizzaType)) { pizza = new CheesePizza(); pizza.setName("cheese"); }else if("greek".equals(pizzaType)) { pizza = new GreekPizza(); pizza.setName("greek"); }else { break; } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } } } // 5、购买披萨 public class PizzaStore { public static void main(String[] args) { // 创建披萨订单实例 OrderPizza orderPizza = new OrderPizza(); // 订购披萨 orderPizza.getPizza(); } }

如果现在需要新增一个新种类的披萨,那么

3.1.2、简单工厂实现

// 披萨抽象类 public abstract class Pizza { protected String name; public void setName(String name) { this.name = name; } public abstract void prepare(); public void bake() { System.out.println(name "baking;"); } public void cut() { System.out.println(name "cut;"); } public void box() { System.out.println(name "box;"); } } // 希腊披萨 public class GreekPizza extends Pizza{ @Override public void prepare() { System.out.println(name "披萨准备原材料"); } } // 奶酪披萨 public class CheesePizza extends Pizza{ @Override public void prepare() { System.out.println(name " 准备原材料"); } } // 披萨工厂根据用户的而不同需求,生产不同类别的披萨 public class SimpleFactory { public Pizza createPizza(String pizzaType) { Pizza pizza = null; if("cheese".equals(pizzaType)) { pizza = new CheesePizza(); pizza.setName("cheese"); }else if("greek".equals(pizzaType)) { pizza = new GreekPizza(); pizza.setName("greek"); } return pizza; } } // 订购披萨 public class OrderPizza { private static Scanner scanner = new Scanner(System.in); private SimpleFactory simpleFactory; // 传入工厂 public OrderPizza(SimpleFactory simpleFactory) { this.simpleFactory = simpleFactory; } public void getPizza() { while (true) { System.out.println("请输入披萨类型:"); // 从工厂获取披萨 Pizza pizza = simpleFactory.createPizza(scanner.next()); if (pizza != null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }else { System.out.println("暂无该类型披萨!"); break; } } } } // 购买披萨 public class PizzaStore { public static void main(String[] args) { // 创建披萨订购类,并传入披萨工厂 OrderPizza orderPizza = new OrderPizza(new SimpleFactory()); orderPizza.getPizza(); } }

这样如果要新增一个种类的披萨,则需要修改

这里如果是编码功底比较弱的同学,多思考思考,可以不着急往下看,一定要理解透。这样我们修改的地方是不是就少了?是不是相比于原来的实现方式提高了一些扩展性

3.1.3、静态工厂改进

其实就是将工厂类中获取实例的方法改为静态的,方便调用

public class SimpleFactory { public static Pizza createPizza(String pizzaType) { Pizza pizza = null; if("cheese".equals(pizzaType)) { pizza = new CheesePizza(); pizza.setName("cheese"); }else if("greek".equals(pizzaType)) { pizza = new GreekPizza(); pizza.setName("greek"); } return pizza; } }

3.2、简单工厂优缺点

优点

缺点

3.3、简单工厂应用场景

对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。

简单工厂模式的结构

在以上案例中,SimpleFactory 这个类就是一个简单工厂,Pizza 这个类就是一个抽象产品,因为不知道要哪一个具体的披萨,CheesePizza 和 GreekPizza 是两个具体的产品,也是最终工厂要生产的具体产品,以及后边可能还会有什么胡椒披萨,水果披萨等等子产品。

工厂中接收的是抽象类,这样可以接收各种类型的披萨,这不就是 依赖倒置原则 么

四、工厂方法模式

简单工厂模式违背了开闭原则,即新增一个类型的产品时就要新增一个产品类和一个工厂类。比如要生产蛋糕了,面包了。而工厂方法模式是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原代码的情况下引进新产品,满足开闭原则。

4.1、实现方式

工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成

// 抽象工厂类 public abstract class PizzaFactory { abstract Pizza createPizza(String name); } // 抽象产品类 public abstract class Pizza { protected String name; public void setName(String name) { this.name = name; } public abstract void prepare(); public void bake() { System.out.println(name "=baking;"); } public void cut() { System.out.println(name "=cut;"); } public void box() { System.out.println(name "=box;"); } } // 具体工厂1 public class BJPizzaFactory extends PizzaFactory{ @Override Pizza createPizza(String name) { Pizza pizza = null; if("pepper".equals(name)) { pizza = new BJPepperPizza(); }else if("cheese".equals(name)) { pizza = new BJCheesePizza(); } return pizza; } } // 具体工厂2 public class LDPizzaFactory extends PizzaFactory{ @Override Pizza createPizza(String name) { Pizza pizza = null; if("pepper".equals(name)) { pizza = new LDPepperPizza(); }else if("cheese".equals(name)) { pizza = new LDCheesePizza(); } return pizza; } } // 工厂1生产产品 public class BJCheesePizza extends Pizza{ @Override public void prepare() { System.out.println("给 " name " 披萨准备原材料"); } } public class BJPepperPizza extends Pizza{ @Override public void prepare() { System.out.println("给 " name " 披萨准备原材料"); } } // 工厂2生产产品 public class LDCheesePizza extends Pizza{ @Override public void prepare() { System.out.println("给 " name " 披萨准备原材料"); } } public class LDPepperPizza extends Pizza{ @Override public void prepare() { System.out.println("给 " name " 披萨准备原材料"); } } // 订购产品 public class OrderPizza { private static Scanner scanner = new Scanner(System.in); private PizzaFactory pizzaFactory; public OrderPizza(PizzaFactory pizzaFactory) { this.pizzaFactory = pizzaFactory; } public void getPizza() { while (true) { System.out.println("请输入披萨类型"); String name = scanner.next(); Pizza pizza = pizzaFactory.createPizza(name); if(pizza != null) { pizza.setName(name); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.bake(); }else { System.out.println("没有这种披萨"); break; } } } } // 测试类 public class PizzaStore { public static void main(String[] args) { // 只需要知道用哪个工厂即可 OrderPizza orderPizza = new OrderPizza(new BJPizzaFactory()); orderPizza.getPizza(); } }

当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到简单工厂模式

4.2、工厂方法模式优缺点

优点

缺点

4.3、工厂方法模式应用场景五、抽象工厂模式

抽象工厂模式是一种为访问类提供一个创建`一组相关或相互依赖对象的接口`,且访问类无需指定所需产品的具体类就可以得到同族的不同等级的产品模式结构。抽象工厂模式是工厂方法模式的升级版,工厂方法模式只生产一个等级的产品(比如只生产电视、手机),而抽象工厂模式可以生产多个等级的产品(一个工厂可以生产手机也可以生产电视等)。

设计单例模式主要考虑的要点(23种设计模式之单例模式)(1)

5.1、实现方式

抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同

// 1、抽象产品 // 抽象手机产品 public interface IPhoneProduct { void start(); void shoutdown(); void call(); void sendSMS(); } // 抽象路由器产品 public interface IRouter { void start(); void shoutdown(); void wifi(); void setting(); } // 2、具体产品 // 小米手机 public class XiaomiPhone implements IPhoneProduct{ @Override public void start() { System.out.println("小米手机开机"); } @Override public void shoutdown() { System.out.println("小米手机关机"); } @Override public void call() { System.out.println("小米手机打电话"); } @Override public void sendSMS() { System.out.println("小米手机发短信"); } } // 小米路由器 public class XiaomiRouter implements IRouter{ @Override public void start() { System.out.println("小米路由器开机"); } @Override public void shoutdown() { System.out.println("小米路由器关机"); } @Override public void wifi() { System.out.println("小米路由器发射信号"); } @Override public void setting() { System.out.println("小米路由器设置"); } } // 华为手机 public class HuaweiPhone implements IPhoneProduct{ @Override public void start() { System.out.println("华为手机开机"); } @Override public void shoutdown() { System.out.println("华为手机关机"); } @Override public void call() { System.out.println("华为手机打电话"); } @Override public void sendSMS() { System.out.println("华为手机发短信"); } } // 华为路由器 public class HuaweiRouter implements IRouter{ @Override public void start() { System.out.println("华为路由器开机"); } @Override public void shoutdown() { System.out.println("华为路由器关机"); } @Override public void wifi() { System.out.println("华为路由器发射信号"); } @Override public void setting() { System.out.println("华为路由器设置"); } } // 3、抽象工厂 public interface IFactory { IPhoneProduct createPhone(); IRouter createRouter(); } // 4、具体工厂 // 小米工厂 public class XiaomiFactory implements IFactory{ @Override public IPhoneProduct createPhone() { return new XiaomiPhone(); } @Override public IRouter createRouter() { return new XiaomiRouter(); } } // 华为工厂 public class HuaweiFactory implements IFactory{ @Override public IPhoneProduct createPhone() { return new HuaweiPhone(); } @Override public IRouter createRouter() { return new HuaweiRouter(); } } // 5、使用工厂 public class Client { public static void main(String[] args) { System.out.println("============小米工厂=========="); XiaomiFactory xiaomiFactory = new XiaomiFactory(); IPhoneProduct phone = xiaomiFactory.createPhone(); phone.start(); phone.shoutdown(); phone.sendSMS(); phone.call(); xiaomiFactory.createRouter(); } }

另一方面,当系统中只存在一个等级结构的产品时(比如只生产电视了),抽象工厂模式将退化到工厂方法模式

5.2、抽象工厂优缺点

优点

缺点

5.3、抽象工厂应用场景5.4、三种工厂模式总结5.4.1、简单工厂模式

又称 静态工厂模式,可以通过一个工厂类创建并提供某一个产品对象,当需要新增一个同类产品时,需要修改工厂代码实现。不符合开闭原则

5.4.2、工厂方法模式

通过对工厂类抽象,具体的工厂类实现抽象的工厂类实现新增同类产品时不需要修改原工厂,只需要新建工厂类创建产品即可。但是弊端在于会产生大量的工厂类

5.4.3、抽象工厂模式

以上两种工厂模式都是创建同一类产品,如果我们要生产不同种类的产品则需要继续抽象工厂,我们可以直接将工厂抽象,不通的厂商可以实现抽象工厂,进而生产产品。

总的来说,开发时简单工厂的应用是最多的,虽然它不符合开闭原则,但是在开发难度、系统架构、功能实现上来说是最优的

六、原型模式

原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果使用系统镜像复制就快了很多。

6.1、实现方式

由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

原型模式包含以下主要角色。

6.1.1、原始方式实现对象克隆

// 1、原始对象 public class Sheep { private String name; private int age; public Sheep(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Sheep{" "name='" name '\'' ", age=" age '}'; } } // 测试类 public class Client { public static void main(String[] args) { // 1、创建一个对象 Sheep sheep1 = new Sheep("喜洋洋", 3); // 2、创建对象,将原始对象的数据存进去,很傻吧~~~ Sheep sheep2 = new Sheep(sheep1.getName(), sheep1.getAge()); Sheep sheep3 = new Sheep(sheep1.getName(), sheep1.getAge()); Sheep sheep4 = new Sheep(sheep1.getName(), sheep1.getAge()); Sheep sheep5 = new Sheep(sheep1.getName(), sheep1.getAge()); Sheep sheep6 = new Sheep(sheep1.getName(), sheep1.getAge()); // 通过hashCode判断是否一样 System.out.println(sheep1 "hashCode = " sheep1.hashCode()); System.out.println(sheep2 "hashCode = " sheep2.hashCode()); System.out.println(sheep3 "hashCode = " sheep3.hashCode()); System.out.println(sheep4 "hashCode = " sheep4.hashCode()); System.out.println(sheep5 "hashCode = " sheep5.hashCode()); System.out.println(sheep6 "hashCode = " sheep6.hashCode()); } }

以上方式实现的优点在于便于理解。缺点在于:

6.1.2、clone方法实现

// 原始对象,实现Cloneable接口,重写 clone 方法 public class Sheep implements Cloneable{ private String name; private int age; public Sheep(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Sheep{" "name='" name '\'' ", age=" age '}'; } // 重写clone方法, @Override protected Object clone() throws CloneNotSupportedException { // 将Sheep返回 Sheep sheep = null; sheep = (Sheep) super.clone(); return sheep; } } // 测试类 public class Clent { public static void main(String[] args) throws CloneNotSupportedException { Sheep sheep = new Sheep("喜洋洋", 12); Sheep sheep1 = (Sheep)sheep.clone(); Sheep sheep2 = (Sheep)sheep.clone(); Sheep sheep3 = (Sheep)sheep.clone(); Sheep sheep4 = (Sheep)sheep.clone(); Sheep sheep5 = (Sheep)sheep.clone(); System.out.println(sheep); System.out.println(sheep1); System.out.println(sheep2); System.out.println(sheep3); System.out.println(sheep4); System.out.println(sheep5); } }

以上属于浅拷贝,意思是,如果原型类中包含对象,clone仅仅是指向了类中所使用的对象,并没有创建新的对象

public class Sheep implements Cloneable{ private String name; private int age; private Sheep friend; public Sheep(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Sheep getFriend() { return friend; } public void setFriend(Sheep friend) { this.friend = friend; } @Override public String toString() { return "Sheep{" "name='" name '\'' ", age=" age ", friend=" friend '}'; } @Override protected Object clone() throws CloneNotSupportedException { Sheep sheep = null; sheep = (Sheep) super.clone(); return sheep; } }

比如上述代码,Sheep类中引用了一个Sheep类,在拷贝的时候,被引用friend并没有创建新的对象,而是多个对象引用同一个friend。我们可以通过深拷贝解决

6.3.3、clone实现深拷贝

public class Sunwukong implements Cloneable, Serializable { private String name; private int age; public Sunwukong(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Sunwukong{" "name='" name '\'' ", age=" age '}'; } @Override protected Object clone() throws CloneNotSupportedException { return (Sunwukong)super.clone(); } } public class LiuErNiHou implements Cloneable, Serializable { private String name; private Sunwukong sunwukong; public LiuErNiHou(String name, Sunwukong sunwukong) { this.name = name; this.sunwukong = sunwukong; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Sunwukong getSunwukong() { return sunwukong; } public void setSunwukong(Sunwukong sunwukong) { this.sunwukong = sunwukong; } @Override public String toString() { return "LiuErNiHou{" "name='" name '\'' ", sunwukong=" sunwukong '}'; } @Override protected Object clone() throws CloneNotSupportedException { LiuErNiHou liuer = null; liuer = (LiuErNiHou)super.clone(); liuer.sunwukong = (Sunwukong) sunwukong.clone(); return liuer; } } public class Client { public static void main(String[] args) throws CloneNotSupportedException { Sunwukong sunwukong = new Sunwukong("孙悟空", 500); LiuErNiHou liuErNiHou = new LiuErNiHou("六耳猕猴",sunwukong); LiuErNiHou liu1 = (LiuErNiHou)liuErNiHou.clone(); LiuErNiHou liu2 = (LiuErNiHou)liuErNiHou.clone(); LiuErNiHou liu3 = (LiuErNiHou)liuErNiHou.clone(); LiuErNiHou liu4 = (LiuErNiHou)liuErNiHou.clone(); System.out.println(liuErNiHou "hashcode=>" liuErNiHou.getSunwukong().hashCode()); System.out.println(liu1 "hashcode=>" liu1.getSunwukong().hashCode()); System.out.println(liu2 "hashcode=>" liu2.getSunwukong().hashCode()); System.out.println(liu3 "hashcode=>" liu3.getSunwukong().hashCode()); System.out.println(liu4 "hashcode=>" liu4.getSunwukong().hashCode()); } }

6.3.4、序列化实现深拷贝

public class LiuErNiHou implements Serializable { private String name; private Sunwukong sunwukong; public LiuErNiHou(String name, Sunwukong sunwukong) { this.name = name; this.sunwukong = sunwukong; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Sunwukong getSunwukong() { return sunwukong; } public void setSunwukong(Sunwukong sunwukong) { this.sunwukong = sunwukong; } @Override public String toString() { return "LiuErNiHou{" "name='" name '\'' ", sunwukong=" sunwukong '}'; } /** * 序列化克隆 * @return */ public Object serClone() { // 创建流对象 ByteArrayInputStream bis = null; ByteArrayOutputStream bos = null; ObjectInputStream ois = null; ObjectOutputStream oos = null; try { // 序列化 bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); // 反序列化 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); return (LiuErNiHou)ois.readObject(); }catch (Exception e) { return null; }finally { try { bos.close(); oos.close(); bis.close(); ois.close(); } catch (IOException e) { e.printStackTrace(); } } } }

推荐使用序列化方式实现深克隆

6.2、原型模式优缺点

优点

缺点

6.3、原型模式应用场景

原型模式通常适用于以下场景。

在 Spring 中,原型模式应用的非常广泛,例如 scope='prototype'、JSON.parseObject() 等都是原型模式的具体应用。

七、建造者模式

在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。

建造者模式的定义是将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,`它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成`。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

7.1、实现方式

建造者模式由产品、抽象建造者、具体建造者、指挥者4部分组成

我们以建造房子为例

7.1.1、原始方式

// 产品抽象类 public abstract class AbstractHouse { // 打地基 public abstract void buildBase(); // 建墙体 public abstract void buildWalls(); // 封顶 public abstract void roofed(); // 构建 public void build() { buildBase(); buildWalls(); roofed(); } } // 具体产品:普通房子 public class CommonHouse extends AbstractHouse{ @Override public void buildBase() { System.out.println("普通房子打10米地基"); } @Override public void buildWalls() { System.out.println("普通房子砌10cm墙体"); } @Override public void roofed() { System.out.println("普通房子用瓦片封顶"); } } // 具体产品:高层建筑 public class HeightBuilding extends AbstractHouse{ @Override public void buildBase() { System.out.println("普通房子打100米地基"); } @Override public void buildWalls() { System.out.println("普通房子砌20cm墙体"); } @Override public void roofed() { System.out.println("普通房子用钢筋混凝土封顶"); } } // 客户端 public class Client { public static void main(String[] args) { CommonHouse commonHouse = new CommonHouse(); commonHouse.build(); } }

以上方式优点在于比较好理解,容易操作。但是该方式将产品本身和创建过程封装在一起,耦合性太强。我们可以使用建造者模式将产品和产品的建造过程解耦

7.1.2、建造者模式实现

// 产品 public class House { private String baise; private String wall; private String roofed; public String getBaise() { return baise; } public void setBaise(String baise) { this.baise = baise; } public String getWall() { return wall; } public void setWall(String wall) { this.wall = wall; } public String getRoofed() { return roofed; } public void setRoofed(String roofed) { this.roofed = roofed; } } // 抽象建造者 public abstract class HouseBuilder { // 创建的产品 protected House house = new House(); // 构建房子流程 public abstract void buildBasic(); public abstract void buildWalls(); public abstract void roofed(); public House getResult() { return house; } } // 具体建造者,建造普通住宅 public class CommonHouse extends HouseBuilder{ @Override public void buildBasic() { System.out.println("普通房子打地基5米"); } @Override public void buildWalls() { System.out.println("普通房子打砌墙10cm"); } @Override public void roofed() { System.out.println("普通房子盖屋顶"); } } // 具体建造者,建造高层建筑 public class HighBuilding extends HouseBuilder{ @Override public void buildBasic() { System.out.println("高层建筑打地基100米"); } @Override public void buildWalls() { System.out.println("高层建筑砌墙20cm"); } @Override public void roofed() { System.out.println("高层建筑盖屋顶"); } } // 指挥者 public class HouseDirector { HouseBuilder houseBuilder = null; public HouseDirector(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } public void setHouseBuilder(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } // 如何建造房子由指挥者决定 public House constructHouse() { houseBuilder.buildBasic(); houseBuilder.buildWalls(); houseBuilder.roofed(); // 获取产品 return houseBuilder.getResult(); } } // 使用者 public class Client { public static void main(String[] args) { CommonHouse commonHouse = new CommonHouse(); // 创建房子指挥者 HouseDirector houseDirector = new HouseDirector(commonHouse); House house = houseDirector.constructHouse(); System.out.println("=======创建高层建筑======="); houseDirector.setHouseBuilder(new HighBuilding()); House house1 = houseDirector.constructHouse(); } }

7.2、建造者模式优缺点

优点

缺点

建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。

7.3、建造者模式应用场景

建造者模式唯一区别于工厂模式的是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式。

当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。

7.4、建造者模式和工厂模式区别总结

本章节介绍了`创建型设计模式`,这些设计模式在实际开发中还都是比较常见的。而且设计模式是对思想上的一个升华,转变,需要时间来消化吸收,建议通过理论和代码实现来理解。再结合之前介绍的 `设计思想`来思考这些设计模式用到了哪些设计思想。

需要特别注意的是:设计模式的编码方式并不是固定的死格式,理解了它的理论和目的之后,根据实际场景编写程序。在程序复杂度和程序实现之间取舍。

,