设计模式

使用设计模式,可以让我们的代码具有更好的可读性、可扩展性、可读性、重用性、符合高内聚低耦合的特点。作为程序员,是我们经常听到的概念,也是我们程序员必须深入学习,了解的知识。

设计模式种类

该表和图来源于菜鸟教程

序号 模式 & 描述 包括
1 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern)
2 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern)
3 行为型模式 这些设计模式特别关注对象之间的通信。 责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern)
4 J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 MVC 模式(MVC Pattern) 业务代表模式(Business Delegate Pattern) 组合实体模式(Composite Entity Pattern) 数据访问对象模式(Data Access Object Pattern) 前端控制器模式(Front Controller Pattern) 拦截过滤器模式(Intercepting Filter Pattern) 服务定位器模式(Service Locator Pattern) 传输对象模式(Transfer Object Pattern)

下面用一个图片来整体描述一下设计模式之间的关系:

七大原则

1、单一职责原则(Single Responsibility Principle)

模块的组成元素之间的功能相关性。一个类只负责一项职责。

2、里氏代换原则(Liskov Substitution Principle)

面向对象设计的基本原则之一。任何基类可以出现的地方,子类一定可以出现,而派生类也能够在基类的基础上增加新的行为。鸡肋设定好了规范,虽然不强制要求子类必须遵循这些契约,但是也不可任意修改,会降低程序的移植性,增加程序的耦合性(基类的修改会影响到所有子类)

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,依赖于抽象而不依赖于具体,核心思想面向接口编程。

4、接口隔离原则(Interface Segregation Principle)

可以增加接口数,来保证一个类依赖于另一个类的最小接口,要为各个类建立它们需要的专用接口。比如B依赖于A的1、2接口,C依赖于A的3、4接口,就可以把1、2接口隔离出来作为新的接口。

5、迪米特法则,又称最少知道原则(Demeter Principle)

一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

尽量使用合成/聚合的方式,而不是使用继承。

7、开闭原则(Open Close Principle)

对扩展开放,对修改关闭。通过使用接口和抽象类来实现在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

单一职责原则

单一职责原则表示:模块的组成元素之间的功能相关性。一个类只负责一项职责。

起初,业务只有一种类型的动物,食草类动物,我们的代码这样写的。

package com.wangscaler.singleresponsibility;/*** @author wangscaler* @date 2021.06.16 10:25*/
public class SingleResponsibilityPrinciple {public static void main(String[] args) {Animal animal = new Animal();animal.eat("牛");animal.eat("羊");animal.eat("马");}
}class Animal {public void eat(String type) {System.out.println(type + "吃草");}
}

这完全没问题的,随着业务的增长,发现我们增加了食肉类动物,如果我们直接写animal.eat("老虎");这就违反了我们单一职责的原则,显然是不合适的。那么我们的代码就需要重写。

正确案例一如下:

package com.wangscaler.singleresponsibility;/*** @author wangscaler* @date 2021.06.16 10:25*/
public class SingleResponsibilityPrinciple {public static void main(String[] args) {Carnivorous carnivorous = new Carnivorous();Herbivorous herbivorous = new Herbivorous();herbivorous.eat("牛");herbivorous.eat("羊");herbivorous.eat("马");carnivorous.eat("老虎");carnivorous.eat("狮子");}
}
class Herbivorous {public void eat(String type) {System.out.println(type + "吃草");}
}class Carnivorous {public void eat(String type) {System.out.println(type + "吃肉");}
}

这样改,显然是符合单一职责原则的,在类下有很多方法时,可以采取使用。在这里方法较少,这样写的话,开销比较大,所以可以违法类的单一职责原则,让方法保持单一职责原则。

正确案例二:

package com.wangscaler.singleresponsibility;/*** @author wangscaler* @date 2021.06.16 10:25*/
public class SingleResponsibilityPrinciple2 {public static void main(String[] args) {Animals animals = new Animals();animals.herbivorousEat("牛");animals.carnivorousEat("老虎");}
}class Animals {public void herbivorousEat(String type) {System.out.println(type + "吃草");}public void carnivorousEat(String type) {System.out.println(type + "吃肉");}
}

采用这种写法的话,当我们增加种类,只需要添加新的种类的方法,而之前的代码是不需要动的。比如此时我们增加既吃肉又吃草的动物。只需要这样修改。

package com.wangscaler.singleresponsibility;/*** @author wangscaler* @date 2021.06.16 10:25*/
public class SingleResponsibilityPrinciple2 {public static void main(String[] args) {Animals animals = new Animals();animals.herbivorousEat("牛");animals.carnivorousEat("老虎");//不要计较熊猫吃不吃肉和草,杠精绕路。animals.eat("熊猫");}
}class Animals {public void herbivorousEat(String type) {System.out.println(type + "吃草");}public void carnivorousEat(String type) {System.out.println(type + "吃肉");}public void eat(String type) {System.out.println(type + "既吃肉还吃草");}
}

我们可以看到,现在只需要增加新业务的代码,之前写的代码是没有影响的。

总结: 单一职责原则的关键在于将业务中类的不同职责分离划分到不同的类或者接口。原则就是一个方法尽可能的处理一个职责,当然职责间肯定是有关联的,这就需要根据业务和需求来划分隔离了。如果方法较多,最好是在类上隔离,如果方法少且逻辑简单的情况下,可以让类违背单一职责原则,让方法保持该原则。

接口隔离原则

可以增加接口数,来保证一个类依赖于另一个类的最小接口,要为各个类建立它们需要的专用接口,接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加轻便灵活。比如B依赖于A的1、2接口,C依赖于A的3、4接口,就可以把1、2接口隔离出来作为新的接口;3、4隔离出来作为新的接口。

错误示例:

起初我们的接口是写了所有的行为,然而马只依赖于eat、run;鸭子依赖于eat、run、swim;天鹅依赖于所有的方法。对于马来说fly方法显然是没用的方法也得实现,这样将不符合逻辑。

package com.wangscaler.interfacesegregation;/*** @author wangscaler* @date 2021.06.16 16:35*/
public class InterfaceSegregationPrinciple {public static void main(String[] args) {IAction horse = new Horse();horse.eat();}interface IAction {void eat();void fly();void run();void swim();}static class Horse implements IAction {public void eat() {System.out.println("马会吃");}public void fly() {}public void run() {System.out.println("马会走");}public void swim() {}}class Duck implements IAction {public void eat() {System.out.println("鸭子会吃");}public void fly() {}public void run() {System.out.println("鸭子会走");}public void swim() {System.out.println("鸭子会游泳");}}class swan implements IAction {public void eat() {System.out.println("天鹅会吃");}public void fly() {System.out.println("天鹅会飞");}public void run() {System.out.println("天鹅会走");}public void swim() {System.out.println("天鹅会游泳");}}
}

我们发现所有的动物均有eat、run的行为,而fly和swim是特有的行为,所以将接口按照接口隔离原则改变后如下:

正确示例。

package com.wangscaler.interfacesegregation;/*** @author wangscaler* @date 2021.06.16 16:35*/
public class InterfaceSegregationPrinciple1 {public static void main(String[] args) {Horse horse = new Horse();horse.eat();horse.run();}interface IEatAndRunAction {void eat();void run();}interface IFlyAction {void fly();}interface ISwimAction {void swim();}static class Horse implements IEatAndRunAction {public void eat() {System.out.println("马会吃");}public void run() {System.out.println("马会走");}}class Duck implements IEatAndRunAction, ISwimAction {public void eat() {System.out.println("鸭子会吃");}public void run() {System.out.println("鸭子会走");}public void swim() {System.out.println("鸭子会游泳");}}class swan implements IEatAndRunAction, ISwimAction, IFlyAction {public void eat() {System.out.println("天鹅会吃");}public void fly() {System.out.println("天鹅会飞");}public void run() {System.out.println("天鹅会走");}public void swim() {System.out.println("天鹅会游泳");}}
}

**总结:**接口的粒度一定要合理,太小就会导致接口增多,太大会导致灵活性降低,减少代码的冗余,提高系统的内聚性。

本文由 简悦 SimpRead 转码, 原文地址 juejin.cn

在设计模式一中,我们已经讲了两种设计原则,接下来继续讲解。 本文讲解的是依赖倒转原则和里氏替换原则。

在设计模式一我们已经讲了两种设计原则,接下来继续讲解。

依赖倒转原则

依赖于抽象而不依赖于具体,核心思想面向接口编程。目的是制定好规范(设计),而不是实现。

比如我们现在写的代码要自动化部署到服务器,开始我们使用的 github 进行的自动化部署。实现过程就是程序员 --> 提交代码到 github–> 自动化部署。

程序员 --> 提交代码

github–> 自动化部署

错误示例

package com.wangscaler.dependenceinversion;/*** @author wangscaler* @date 2021.06.16 17:43*/
public class DependenceInversionPrinciple {public static void main(String[] args) {Programmer programmer = new Programmer();programmer.commit(new Github());}static class Github {public String cicd() {return "github 自动化部署完成";}}static class Programmer {public void commit(Github github) {System.out.println(github.cicd());}}
}

有一天,github 仓库访问太慢了,我们不想用了,换成 gitlab,这时候我们新建一个 Gitlab 仓库并加上的 cicd 方法,但是我们虽然有了这个仓库,却没法自动化部署,因为我们的程序员只知道 github。此时 Programmer 依赖了 Github,这样是不合理的,模块与模块之间耦合度太高,生产力太低。

正确示例

package com.wangscaler.dependenceinversion;/*** @author wangscaler* @date 2021.06.16 17:43*/
public class DependenceInversionPrinciple1 {public static void main(String[] args) {Programmer programmer = new Programmer();programmer.commit(new Gitlab());}static class Github implements IWarehouse {public String cicd() {return "github 自动化部署完成";}}static class Gitlab implements IWarehouse {public String cicd() {return "gitlab 自动化部署完成";}}public interface IWarehouse {public String cicd();}static class Programmer {public void commit(IWarehouse warehouse) {System.out.println(warehouse.cicd());}}
}

因为 github 和 gitlab 都属于仓库,而且都是有 cicd 的方法,所以定义仓库接口,让他们实现这个接口就行了。如果再后来,又想换成华为云仓库,只需要增加华为云这个类就行了。

package com.wangscaler.dependenceinversion;/*** @author wangscaler* @date 2021.06.16 17:43*/
public class DependenceInversionPrinciple1 {public static void main(String[] args) {Programmer programmer = new Programmer();programmer.commit(new Huawei());}static class Github implements IWarehouse {public String cicd() {return "github 自动化部署完成";}}static class Gitlab implements IWarehouse {public String cicd() {return "gitlab 自动化部署完成";}}static class Huawei implements IWarehouse {public String cicd() {return "华为云仓库自动化部署完成";}}public interface IWarehouse {public String cicd();}static class Programmer {public void commit(IWarehouse warehouse) {System.out.println(warehouse.cicd());}}
}

总结: 根据依赖倒转原则,我们关注的抽象而不是具体。在这个例子当中,我们的设计思路应该是程序员 --> 提交代码到仓库 --> 自动化部署。无论是 github、gitlab 还是华为云,他们抽象出来都是仓库即从下层模块 github 开始,想想看他能抽象化出什么。这个抽象类就相当于一个缓冲层,增强代码的可扩展性。

里氏替换原则

所有引用基类的地方都能透明的使用他的子类。面向对象设计的基本原则之一。任何基类可以出现的地方,子类一定可以出现,而派生类也能够在基类的基础上增加新的行为。会降低程序的移植性,增加程序的耦合性(基类的修改会影响到所有子类)。子类继承父类时,尽量不要重写父类的方法。

比如我们有两个鸟,燕子和奇异鸟 (不会飞),本来我们常识鸟都是飞的,所以代码这样写的

package com.wangscaler.liskovsubstitution;
/*** @author wangscaler* @date 2021.06.17*/
public class LiskovSubstitutionPrinciple {public static void main(String[] args) {Swallow swallow = new Swallow();Kiwi kiwi = new Kiwi();swallow.setSpeed(110);kiwi.setSpeed(120);System.out.println(swallow.getFlyTime(240));System.out.println(kiwi.getFlyTime(240));}static class Bird {double speed;public void setSpeed(double speed) {this.speed = speed;}public double getFlyTime(double distance) {return (distance / speed);}}static class Swallow extends Bird {}static class Kiwi extends Bird {@Overridepublic void setSpeed(double speed) {speed = 0;}}
}

执行结果

2.1818181818181817
Infinity

因为奇异鸟不会飞,所以改写了父类的方法,导致我们在父类设置速度,理所当然认为在奇异鸟中设置速度也是这个,最终导致错误的产生,我们应该设置更基础的父类,来避免字类继承的时候重写父类的方法。

package com.wangscaler.liskovsubstitution;/*** @author wangscaler* @date 2021.06.17*/
public class LiskovSubstitutionPrinciple {public static void main(String[] args) {Swallow swallow = new Swallow();Kiwi kiwi = new Kiwi();swallow.setFlySpeed(110);kiwi.setSpeed(120);System.out.println(swallow.getFlyTime(240));System.out.println(kiwi.getTime(240));}static class Animal {double speed;public void setSpeed(double speed) {this.speed = speed;}public double getTime(double distance) {return (distance / speed);}}static class Bird extends Animal {double flyspeed;public void setFlySpeed(double speed) {this.flyspeed = speed;}public double getFlyTime(double distance) {return (distance / flyspeed);}}static class Swallow extends Bird {}static class Kiwi extends Animal {}
}

总结: 里氏替换原则就是要求我们集成基类,尽量不要重写父类,可以增加功能。如果必须要重写父类方法,一定要符合输入条件比父类更加宽松,输出条件比父类更加严格的要求。比如一开始的代码,父类 Bird 的输入条件是范围,而子类 Kiwi 的输入条件变成了 0 显然是不符合规则的。

开闭原则

最重要最基础的原则,对扩展开放,对修改关闭。通过使用接口和抽象类来实现在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

比如我们绘制图形,起初我们只绘制矩形和圆形,代码是这样写的

package com.wangscaler.openclose;/*** @author wangscaler* @date 2021.06.17 11:14*/public class OpenClosePrinciple {public static void main(String[] args) {Graphic graphic = new Graphic();graphic.drawRectangle(new Rectangle());graphic.drawCircular(new Circular());}static class Graphic {public void drawShape(Shape shape) {if (shape.type == 1) {drawCircular(shape);} else if (shape.type == 2) {drawRectangle(shape);}}public void drawRectangle(Shape shape) {System.out.println("绘制矩形...");}public void drawCircular(Shape shape) {System.out.println("绘制圆形...");}}static class Shape {int type;}static class Circular extends Shape {Circular() {super.type = 1;}}static class Rectangle extends Shape {Rectangle() {super.type = 2;}}
}

当我们添加新的图形梯形的时候,按照这种方式的话代码就得这样改

错误方式

package com.wangscaler.openclose;/*** @author wangscaler* @date 2021.06.17 11:14*/public class OpenClosePrinciple1 {public static void main(String[] args) {Graphic graphic = new Graphic();graphic.drawRectangle(new Rectangle());graphic.drawCircular(new Circular());graphic.drawTrapezoid(new Trapezoid());}static class Graphic {public void drawShape(Shape shape) {if (shape.type == 1) {drawCircular(shape);} else if (shape.type == 2) {drawRectangle(shape);}else if(shape.type == 3){drawTrapezoid(shape);}}public void drawRectangle(Shape shape) {System.out.println("绘制矩形...");}public void drawCircular(Shape shape) {System.out.println("绘制圆形...");}public void drawTrapezoid(Shape shape) {System.out.println("绘制梯形...");}}static class Shape {int type;}static class Circular extends Shape {Circular() {super.type = 1;}}static class Rectangle extends Shape {Rectangle() {super.type = 2;}}static class Trapezoid extends Shape {Trapezoid() {super.type = 3;}}
}

这样虽然可以拿到正确结果,但是改的代码较多,并不能实现热拔插效果,我们不仅修改了提供方的代码(增加了 Trapezoid),还修改了使用方的代码(Graphic)。我们应该将 Shape 变成抽象类,有新的图形时,只需要增加新的图形类去继承这个抽象类就可以了

正确的方式

package com.wangscaler.openclose;/*** @author wangscaler* @date 2021.06.17 11:14*/public class OpenClosePrinciple2 {public static void main(String[] args) {Graphic graphic = new Graphic();graphic.drawShape(new Rectangle());graphic.drawShape(new Circular());}static class Graphic {public void drawShape(Shape shape) {shape.draw();}}static abstract class Shape {public abstract void draw();}static class Circular extends Shape {@Overridepublic void draw() {System.out.println("绘制圆形...");}}static class Rectangle extends Shape {@Overridepublic void draw() {System.out.println("绘制矩形...");}}
}

这时候我们增加新的图形矩形,只需要修改成

package com.wangscaler.openclose;/*** @author wangscaler* @date 2021.06.17 11:14*/public class OpenClosePrinciple3 {public static void main(String[] args) {Graphic graphic = new Graphic();graphic.drawShape(new Rectangle());graphic.drawShape(new Circular());graphic.drawShape(new Trapezoid());}static class Graphic {public void drawShape(Shape shape) {shape.draw();}}static abstract class Shape {public abstract void draw();}static class Circular extends Shape {@Overridepublic void draw() {System.out.println("绘制圆形...");}}static class Rectangle extends Shape {@Overridepublic void draw() {System.out.println("绘制矩形...");}}static class Trapezoid extends Shape {@Overridepublic void draw() {System.out.println("绘制梯形...");}}
}

总结: 开闭原则对扩展开放,对修改关闭。我们新增梯形,只需要新增加梯形这个类,让我们的梯形去继承 shape 抽象类并实现抽象类的方法,此时我们不需要修改原有的代码,就实现了我们想要的结果。

迪米特法则

一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。即只和直接的朋友(方法参数、成员变量、方法返回值)通信。

比如 学校校长想查某个班的人数,所以

package com.wangscaler.leastknowledgeprinciple;import java.util.ArrayList;
import java.util.List;/*** @author wangscaler* @date 2021.06.17 14:09*/
public class LeastKnowledgePrinciple {public static void main(String[] args) {Principal principal = new Principal();principal.commond(new Teacher());}static class Principal {public void commond(Teacher teacher) {List<Student> students = new ArrayList<Student>();for (int i = 0; i < 20; i++) {students.add(new Student());}teacher.count(students);}}static class Teacher {public void count(List<Student> students) {System.out.println("学生的数量是:" + students.size());}}static class Student {}
}

在这里,我们在校长的 commond 的方法里出现了 Student, 他就不是方法参数、成员变量还不是方法返回值,所以说 Student 不是 Principal 的直接朋友,这样就是违反了迪米特法则。那么应该怎么修改呢,因为 Student 和 Teacher 是直接的朋友

package com.wangscaler.leastknowledgeprinciple;import java.util.ArrayList;
import java.util.List;/*** @author wangscaler* @date 2021.06.17 14:09*/
public class LeastKnowledgePrinciple1 {public static void main(String[] args) {List<Student> students = new ArrayList<Student>();for (int i = 0; i < 20; i++) {students.add(new Student());}Principal principal = new Principal();principal.commond(new Teacher(students));}static class Principal {public void commond(Teacher teacher) {teacher.count();}}static class Teacher {private List<Student> students;public Teacher(List<Student> students) {this.students = students;}public void count() {System.out.println("学生的数量是:" + students.size());}}static class Student {}
}

学生成为老师的成员变量,老师和学生是直接朋友,校长和老师是直接朋友。

总结: 从上述例子可以看出,迪米特法则可以降低耦合。尽量避免不是直接的朋友(局部变量)出现在类中。

设计模式 设计原则我们已经讲了六种,单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特法则本篇将介绍最后一种合成复用原则

设计原则我们已经讲了六种,单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特法则本篇将介绍最后一种合成复用原则以及依赖关系传递的三种方式。

合成复用原则

尽量使用合成 / 聚合的方式,而不是使用继承。

一开始 A 有两个方法,B 正好需要这两个方法,此时最直接的做法就是继承。

package com.wangscaler.compositereuseprinciple;/*** @author wangscaler* @date 2021.06.17 14:50*/
public class CompositeReusePrinciple {public static void main(String[] args) {B b = new B();b.test();}static class A {void test() {System.out.println("test");}void test1() {System.out.println("test1");}}static class B extends A {}
}

这样我们就简单的让 B 拥有了 test 和 test1 的功能,但随着业务的发展,A 又新增了 test2、test3 的方法,这对于 B 来说是多余的,这大大增加了耦合性。所以依据合成复用原则,修改代码有如下三种方法。

方式一:合成

package com.wangscaler.compositereuseprinciple;/*** @author wangscaler* @date 2021.06.17 14:50*/
public class CompositeReusePrinciple1 {public static void main(String[] args) {B b = new B();b.test();}static class A {void test() {System.out.println("test");}void test1() {System.out.println("test1");}}static class B {A a = new A();void test() {a.test();}}
}

方式二:依赖

package com.wangscaler.compositereuseprinciple;/*** @author wangscaler* @date 2021.06.17 14:50*/
public class CompositeReusePrinciple2 {public static void main(String[] args) {B b = new B();b.test(new A());}static class A {void test() {System.out.println("test");}void test1() {System.out.println("test1");}}static class B {void test(A a) {a.test();}void test1(A a) {a.test1();}}
}

方式三:聚合

package com.wangscaler.compositereuseprinciple;/*** @author wangscaler* @date 2021.06.17 14:50*/
public class CompositeReusePrinciple3 {public static void main(String[] args) {B b = new B();b.setA(new A());b.test();}static class A {void test() {System.out.println("test");}void test1() {System.out.println("test1");}}static class B {private A a;public void setA(A a) {this.a = a;}void test() {a.test();}}
}

总结: 优先考虑合成 / 聚合,最后考虑继承。聚合组合是一种 “黑箱” 复用,而继承则是白箱,对子类而言都是透明的。

依赖关系传递的三种方式

  • 接口传递
  • 构造方法传递
  • setter 方式传递

接口传递

package com.wangscaler.dependencytransfer;/*** @author wangscaler* @date 2021.06.17 17:14*/
public class DependencyTransfer {public static void main(String[] args) {Driver driver = new Driver();driver.drive(new Civic());}interface IDriver {void drive(ICar car);}interface ICar {void running();}static class Civic implements ICar {public void running() {System.out.println("思域秒天秒地秒空气");}}static class Driver implements IDriver {public void drive(ICar car) {car.running();}}}

在此例中,我们在接口 IDriver 中传递了 ICar,Driver 通过实现了 IDriver 来获取了汽车 running 的功能

构造方法传递

package com.wangscaler.dependencytransfer;/*** @author wangscaler* @date 2021.06.17 17:14*/
public class DependencyTransfer1 {public static void main(String[] args) {Driver driver = new Driver(new Civic());driver.drive();}interface IDriver {void drive();}interface ICar {void running();}static class Civic implements ICar {public void running() {System.out.println("思域秒天秒地秒空气");}}static class Driver implements IDriver {public ICar car;public Driver(ICar car) {this.car = car;}public void drive() {car.running();}}}

和接口传递不同的是,将 Icar 作为 Driver 的成员变量,通过构造器获取 Icar。

setter 方式传递

package com.wangscaler.dependencytransfer;/*** @author wangscaler* @date 2021.06.17 17:14*/
public class DependencyTransfer2 {public static void main(String[] args) {Driver driver = new Driver();driver.setCar(new Civic());driver.drive();}interface IDriver {void drive();}interface ICar {void running();}static class Civic implements ICar {public void running() {System.out.println("思域秒天秒地秒空气");}}static class Driver implements IDriver {public ICar car;public void drive() {car.running();}public void setCar(ICar car) {this.car = car;}}}

为成员变量 ICar,写上 Setter 方法,将 Icar 传入。

节外话:不招黑,我思域就是秒天秒地秒空气。

总结

讲到这里,我们的七大原则也就说完了,设计原则的核心三点:一、找出可能变化的地方,将他们提取出来。二、针对接口编程,关注抽象而不关注具体。三、松耦合。 借用语言中文网的一句话:访问加限制,函数要节俭,依赖不允许,动态加接口,父类要抽象,扩展不更改 只是学这些概念,很难灵活的运用,看到这你也许觉着自己学会了,也许你又觉着自己什么都没学会,实践出真知啊,接下来继续跟着我的专栏设计模式一块研究研究设计模式是如何真正的将这些原则用起来,你也许就真的吃透了。

设计模式 – 单例模式

本篇主要讲解设计模式中的单例模式,你知道哪些单例模式?这些适不适合应用在我们的开发中呢?一起来看看吧。

单例模式

就是整个软件系统中一个类只有一个实例

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检验锁
  • 静态内部类
  • 枚举

饿汉式(静态常量)

package com.wangscaler.singleton;/*** @author wangscaler* @date 2021.06.18 11:18*/public class Hungryman {public static void main(String[] args) {StatiConst statiConst1 = StatiConst.getInstance();StatiConst statiConst2 = StatiConst.getInstance();System.out.println(statiConst2.hashCode());System.out.println(statiConst1.hashCode());}static class StatiConst {//私有化之后,外部不可newprivate StatiConst() {}private final static StatiConst instance = new StatiConst();public static StatiConst getInstance() {return instance;}}
}

总结: 构建饿汉式需要注意

  • 1、构造器私有化private StatiConst() { }
  • 2、类的内部创建对象 private final static StatiConst instance = new StatiConst();
  • 3、向外暴露静态的公共方法public static StatiConst getInstance() { return instance;}
  • 4、这种方式会在类装载的时候完成实例化,所以全局通过 getInstance 拿到的实例对象,永远都是一个, 这种方式是线程安全的。
  • 如果项目中不使用这个实例,就会造成内存的浪费。

饿汉式(静态代码块)

package com.wangscaler.singleton;/*** @author wangscaler* @date 2021.06.18 11:18*/public class Hungryman1 {public static void main(String[] args) {StatiCodeBlock statiConst1 = StatiCodeBlock.getInstance();StatiCodeBlock statiConst2 = StatiCodeBlock.getInstance();System.out.println(statiConst2.hashCode());System.out.println(statiConst1.hashCode());}static class StatiCodeBlock {//私有化之后,外部不可newprivate StatiCodeBlock() {}static {instance = new StatiCodeBlock();}private static StatiCodeBlock instance;public static StatiCodeBlock getInstance() {return instance;}}
}

这种方式创建和静态常量的方式是一样的。

懒汉式(线程不安全)

package com.wangscaler.singleton;/*** @author wangscaler* @date 2021.06.18 14:06*/
public class Lazyman {public static void main(String[] args) {Unsafe instance = Unsafe.getInstance();Unsafe instance2 = Unsafe.getInstance();System.out.println(instance.hashCode());System.out.println(instance2.hashCode());System.out.println(instance == instance2);}static class Unsafe {private static Unsafe instance;private Unsafe() {}public static Unsafe getInstance() {if (instance == null) {instance = new Unsafe();}return instance;}}
}

在单线程中这种方式只有在第一次用到的时候才会创建这个对象。当已经创建过之后,就会返回之前创建的对象。然而在多线程中就有可能初始化出来多个实例,所以说这种方法是线程不安全的,在实际的开发中,切记不要使用这种方式。

懒汉式(线程安全,同步方法)

package com.wangscaler.singleton;/*** @author wangscaler* @date 2021.06.18 14:06*/
public class Lazyman1 {public static void main(String[] args) {Synchronizationmethod instance = Synchronizationmethod.getInstance();Synchronizationmethod instance2 = Synchronizationmethod.getInstance();System.out.println(instance.hashCode());System.out.println(instance2.hashCode());System.out.println(instance == instance2);}static class Synchronizationmethod {private static Synchronizationmethod instance;private Synchronizationmethod() {}public static synchronized Synchronizationmethod getInstance() {if (instance == null) {instance = new Synchronizationmethod();}return instance;}}
}

将 getInstance 方法变成同步的方法,每个线程进来就会阻塞,必须等待上一个线程访问结束之后才能继续访问,效率太低。

懒汉式(线程安全,同步代码块)

package com.wangscaler.singleton;import sun.misc.JavaAWTAccess;/*** @author wangscaler* @date 2021.06.18 14:06*/
public class Lazyman2 {public static void main(String[] args) {Synchronizationcodeblock instance = Synchronizationcodeblock.getInstance();Synchronizationcodeblock instance2 = Synchronizationcodeblock.getInstance();System.out.println(instance.hashCode());System.out.println(instance2.hashCode());System.out.println(instance == instance2);}static class Synchronizationcodeblock {private static Synchronizationcodeblock instance;private Synchronizationcodeblock() {}public static Synchronizationcodeblock getInstance() {if (instance == null) {synchronized (Lazyman2.class) {instance = new Synchronizationcodeblock();}}return instance;}}
}

将同步机制放到代码块,这种方式其实和懒汉式(线程不安全)是一样的,起不到同步的作用,还是会产生多个实例,所以也是没办法使用。

双重检验锁

package com.wangscaler.singleton;public class Doublechecklock {public static void main(String[] args) {Doublecheck doublecheck = Doublecheck.getInstance();Doublecheck doublecheck1 = Doublecheck.getInstance();System.out.println(doublecheck.hashCode());System.out.println(doublecheck1.hashCode());System.out.println(doublecheck == doublecheck1);}static class Doublecheck {private static volatile Doublecheck instance;private Doublecheck() {}public static Doublecheck getInstance() {if (instance == null) {synchronized (Doublecheck.class) {if (instance == null) {instance = new Doublecheck();}}}return instance;}}
}

此时synchronized (Doublecheck.class)可以认为一个门的锁,第一个if (instance == null) 可以认为门前的检查哨,第二个就是门后的检查哨,这个双重检验锁的名字起的是相当准确啊。假如有三个线程通过第一个检查哨,此时当第一个线程拿到钥匙打开门,剩下的两个只能等待,当第一个线程通过第二个检查哨把对象创建出来,并刷新到内存,剩下的线程进来,就无法通过第二个检查哨

可以看到这里使用了一个关键字volatile,这个关键字可以禁止指令重排,这个关键字在 java5 之前是有问题的。

在这里instance = new Doublecheck();其实会产生三个操作。

1、给 instance 分配内存

2、调用构造函数初始化变量

3、将该对象指向为其分配的内存空间

这三个操作会发生指令重排的结果,即 1-2-3/1-3-2。

  • 那么什么是指令重排呢?

指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

简言之,代码的运行不一定是按照编码的顺序,但是它可以根据指令之间的数据依赖性来将没有依赖的指令重排保证结果的一致性的同时提高效率。

如果发生指令重排,即先执行 3 在执行 2 的情况,此时执行完 3 之后,新的线程就会进来,此时 2 还没有执行,那么新线程拿到的对象(执行完 3,此时的对象就不是 null),就会有问题。

  • 为什么出现这个情况?

Java 内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

而使用了volatile之后,不仅会阻止指令重排,还会将修改的值立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

参考资料

  • Java 并发编程:volatile 关键字解析
  • 如何正确地写出单例模式

上节我们讲了一部分单例模式,接下来继续讲解静态内部类和枚举,以及反序列化对单例模式的影响和源码中的单例模式。

上节我们讲了一部分单例模式,接下来继续讲解静态内部类和枚举,以及源码中的单例模式。

静态内部类

package com.wangscaler.singleton;/*** @author wangscaler* @date 2021.06.21 17:42*/
public class Staticinnerclass {public static void main(String[] args) {Staticinnerclass staticinnerclass = Staticinnerclass.getInstance();Staticinnerclass staticinnerclass1 = Staticinnerclass.getInstance();System.out.println(staticinnerclass.hashCode());System.out.println(staticinnerclass1.hashCode());System.out.println(staticinnerclass == staticinnerclass1);}private Staticinnerclass() {}private static class StaticinnerInstance {private static final Staticinnerclass INSTANCE = new Staticinnerclass();}public static Staticinnerclass getInstance() {return StaticinnerInstance.INSTANCE;}
}

Staticinnerclass 装载的时候,静态内部类 StaticinnerInstance 是不会装载的,只有代码调用 getInstance 时,调用他,才会被初始化。这种模式既能保证线程安全又能保证懒加载。所以也推荐使用。

枚举

package com.wangscaler.singleton;/*** @author wangscaler* @date 2021.06.21 17:59*/
public class Enumeration {public static void main(String[] args) {EnumerationSingleton enumerationSingleton = EnumerationSingleton.INSTANCE;EnumerationSingleton enumerationSingleton1 = EnumerationSingleton.INSTANCE;System.out.println(enumerationSingleton.hashCode());System.out.println(enumerationSingleton1.hashCode());System.out.println(enumerationSingleton == enumerationSingleton1);}
}enum EnumerationSingleton {INSTANCE;
}

此种方式,不仅避免多线程同步问题,还防止反序列化重新创建新的对象,也是推荐使用的。 提到反序列化,那么反序列化时,会对单例模式造成什么影响呢?

反序列化对单例模式的影响

package com.wangscaler.singleton;import java.io.*;/*** @author wangscaler* @date 2021.06.18 11:18*/public class Hungryman {public static void main(String[] args) throws IOException, ClassNotFoundException {StatiConst statiConst1 = StatiConst.getInstance();ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization"));outputStream.writeObject(statiConst1);File file = new File("SingletonDeserialization");ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));StatiConst statiConst = (StatiConst) inputStream.readObject();System.out.println(statiConst.hashCode());System.out.println(statiConst1.hashCode());System.out.println(statiConst == statiConst1);}static class StatiConst implements Serializable{//私有化之后,外部不可newprivate StatiConst() {}private final static StatiConst instance = new StatiConst();public static StatiConst getInstance() {return instance;}}
}

执行之后的结果为

false

从上述可以看出,将对象写入文件中,再从文件读出来,反序列化成对象之后,拿到的 hash 并不是之前的对象了。那么我们怎么让反序列化之后的对象还是之前的对象呢?只需要增加方法readResolve,当 JVM 从内存中反序列化地 “组装” 一个新对象时, 就会自动调用这个 readResolve 方法来返回我们指定好的对象了, 从而使得单例规则得到了保证。修改之后的代码

package com.wangscaler.singleton;import java.io.*;/*** @author wangscaler* @date 2021.06.18 11:18*/public class Hungryman {public static void main(String[] args) throws IOException, ClassNotFoundException {StatiConst statiConst1 = StatiConst.getInstance();ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization"));outputStream.writeObject(statiConst1);File file = new File("SingletonDeserialization");ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));StatiConst statiConst = (StatiConst) inputStream.readObject();System.out.println(statiConst.hashCode());System.out.println(statiConst1.hashCode());System.out.println(statiConst == statiConst1);}static class StatiConst implements Serializable{//私有化之后,外部不可newprivate StatiConst() {}private final static StatiConst instance = new StatiConst();public static StatiConst getInstance() {return instance;}private Object readResolve(){return instance;}}
}

执行结果

true

可以看到,我们增加了这个方法之后,就达到了我们的预期效果,那么程序什么时候执行了这段代码? 打开源码我们发现

/** if true, invoke readObjectOverride() instead of readObject() */private final boolean enableOverride;
/*** Read an object from the ObjectInputStream.  The class of the object, the* signature of the class, and the values of the non-transient and* non-static fields of the class and all of its supertypes are read.* Default deserializing for a class can be overridden using the writeObject* and readObject methods.  Objects referenced by this object are read* transitively so that a complete equivalent graph of objects is* reconstructed by readObject.** <p>The root object is completely restored when all of its fields and the* objects it references are completely restored.  At this point the object* validation callbacks are executed in order based on their registered* priorities. The callbacks are registered by objects (in the readObject* special methods) as they are individually restored.** <p>Exceptions are thrown for problems with the InputStream and for* classes that should not be deserialized.  All exceptions are fatal to* the InputStream and leave it in an indeterminate state; it is up to the* caller to ignore or recover the stream state.** @throws  ClassNotFoundException Class of a serialized object cannot be*          found.* @throws  InvalidClassException Something is wrong with a class used by*          serialization.* @throws  StreamCorruptedException Control information in the*          stream is inconsistent.* @throws  OptionalDataException Primitive data was found in the*          stream instead of objects.* @throws  IOException Any of the usual Input/Output related exceptions.*/
public final Object readObject()throws IOException, ClassNotFoundException
{if (enableOverride) {return readObjectOverride();}// if nested read, passHandle contains handle of enclosing objectint outerHandle = passHandle;try {Object obj = readObject0(false);handles.markDependency(outerHandle, passHandle);ClassNotFoundException ex = handles.lookupException(passHandle);if (ex != null) {throw ex;}if (depth == 0) {vlist.doCallbacks();}return obj;} finally {passHandle = outerHandle;if (closed && depth == 0) {clear();}}
}/*** Underlying readObject implementation.*/private Object readObject0(boolean unshared) throws IOException {boolean oldMode = bin.getBlockDataMode();if (oldMode) {int remain = bin.currentBlockRemaining();if (remain > 0) {throw new OptionalDataException(remain);} else if (defaultDataEnd) {/** Fix for 4360508: stream is currently at the end of a field* value block written via default serialization; since there* is no terminating TC_ENDBLOCKDATA tag, simulate* end-of-custom-data behavior explicitly.*/throw new OptionalDataException(true);}bin.setBlockDataMode(false);}byte tc;while ((tc = bin.peekByte()) == TC_RESET) {bin.readByte();handleReset();}depth++;totalObjectRefs++;try {switch (tc) {case TC_NULL:return readNull();case TC_REFERENCE:return readHandle(unshared);case TC_CLASS:return readClass(unshared);case TC_CLASSDESC:case TC_PROXYCLASSDESC:return readClassDesc(unshared);case TC_STRING:case TC_LONGSTRING:return checkResolve(readString(unshared));case TC_ARRAY:return checkResolve(readArray(unshared));case TC_ENUM:return checkResolve(readEnum(unshared));case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared));case TC_EXCEPTION:IOException ex = readFatalException();throw new WriteAbortedException("writing aborted", ex);case TC_BLOCKDATA:case TC_BLOCKDATALONG:if (oldMode) {bin.setBlockDataMode(true);bin.peek();             // force header readthrow new OptionalDataException(bin.currentBlockRemaining());} else {throw new StreamCorruptedException("unexpected block data");}case TC_ENDBLOCKDATA:if (oldMode) {throw new OptionalDataException(true);} else {throw new StreamCorruptedException("unexpected end of block data");}default:throw new StreamCorruptedException(String.format("invalid type code: %02X", tc));}} finally {depth--;bin.setBlockDataMode(oldMode);}}/*** Reads and returns "ordinary" (i.e., not a String, Class,* ObjectStreamClass, array, or enum constant) object, or null if object's* class is unresolvable (in which case a ClassNotFoundException will be* associated with object's handle).  Sets passHandle to object's assigned* handle.*/private Object readOrdinaryObject(boolean unshared)throws IOException{if (bin.readByte() != TC_OBJECT) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);desc.checkDeserialize();Class<?> cl = desc.forClass();if (cl == String.class || cl == Class.class|| cl == ObjectStreamClass.class) {throw new InvalidClassException("invalid class descriptor");}Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}passHandle = handles.assign(unshared ? unsharedMarker : obj);ClassNotFoundException resolveEx = desc.getResolveException();if (resolveEx != null) {handles.markException(passHandle, resolveEx);}if (desc.isExternalizable()) {readExternalData((Externalizable) obj, desc);} else {readSerialData(obj, desc);}handles.finish(passHandle);if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {// Filter the replacement objectif (rep != null) {if (rep.getClass().isArray()) {filterCheck(rep.getClass(), Array.getLength(rep));} else {filterCheck(rep.getClass(), -1);}}handles.setObject(passHandle, obj = rep);}}return obj;}

在我们的程序中,调用inputStream.readObject()之后,他会先判断是否有 readObjectOverride(),如果有则取代 readObject,在这里是没有的,所以程序 往下执行Object obj = readObject0(false);执行完这句代码进入 readObject0 之后,因为我们是对象,所以会走case TC_OBJECT里的readOrdinaryObject(unshared)打开这个方法的源码我们发现有这样一句代码obj = desc.isInstantiable() ? desc.newInstance() : null;先判断该对象是否可以实例化,,如果可以则进行实例化,之后

if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod())

通过desc.hasReadResolveMethod()判断我们是否写了ReadResolve方法,如果我们写了,则调用desc.invokeReadResolve(obj);来调用我们写的ReadResolve方法。打开 invokeReadResolve 的源码如下

/** class-defined readResolve method, or null if none */private Method readResolveMethod;
/*** Invokes the readResolve method of the represented serializable class and* returns the result.  Throws UnsupportedOperationException if this class* descriptor is not associated with a class, or if the class is* non-serializable or does not define readResolve.*/
Object invokeReadResolve(Object obj)throws IOException, UnsupportedOperationException
{requireInitialized();if (readResolveMethod != null) {try {return readResolveMethod.invoke(obj, (Object[]) null);} catch (InvocationTargetException ex) {Throwable th = ex.getTargetException();if (th instanceof ObjectStreamException) {throw (ObjectStreamException) th;} else {throwMiscException(th);throw new InternalError(th);  // never reached}} catch (IllegalAccessException ex) {// should not occur, as access checks have been suppressedthrow new InternalError(ex);}} else {throw new UnsupportedOperationException();}
}

调用完我们写的方法之后,他会判断我们返回的对象,和他刚才生成的对象是否相同

if (rep != obj) {// Filter the replacement objectif (rep != null) {if (rep.getClass().isArray()) {filterCheck(rep.getClass(), Array.getLength(rep));} else {filterCheck(rep.getClass(), -1);}}handles.setObject(passHandle, obj = rep);
}

如果不同,则将我们返回的对象,赋值给他之前生成的对象,从而是我们反序列化之后的对象还是之前的对象。那么为什么枚举类型的不要写这个方法就能拿到这个对象呢

/*** Reads in and returns enum constant, or null if enum type is* unresolvable.  Sets passHandle to enum constant's assigned handle.*/
private Enum<?> readEnum(boolean unshared) throws IOException {if (bin.readByte() != TC_ENUM) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);if (!desc.isEnum()) {throw new InvalidClassException("non-enum class: " + desc);}int enumHandle = handles.assign(unshared ? unsharedMarker : null);ClassNotFoundException resolveEx = desc.getResolveException();if (resolveEx != null) {handles.markException(enumHandle, resolveEx);}String name = readString(false);Enum<?> result = null;Class<?> cl = desc.forClass();if (cl != null) {try {@SuppressWarnings("unchecked")Enum<?> en = Enum.valueOf((Class)cl, name);result = en;} catch (IllegalArgumentException ex) {throw (IOException) new InvalidObjectException("enum constant " + name + " does not exist in " +cl).initCause(ex);}if (!unshared) {handles.setObject(enumHandle, result);}}handles.finish(enumHandle);passHandle = enumHandle;return result;
}

我们可以在源码看到Enum.valueOf((Class)cl, name);,直接根据名字将对象取出来,然后result = en;赋值给之前的对象。

JDK 中的单例模式

以 JDK 中的 Runtime 为例

public class Runtime {private static Runtime getRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return  the <code>Runtime</code> object associated with the current*          Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}
}

可以看到我们 Runtime 的源码就是使用的单例模式中的饿汉式。

public class Desktop {/*** Represents an action type.  Each platform supports a different* set of actions.  You may use the {@link Desktop#isSupported}* method to determine if the given action is supported by the* current platform.* @see java.awt.Desktop#isSupported(java.awt.Desktop.Action)* @since 1.6*/public static enum Action {/*** Represents an "open" action.* @see Desktop#open(java.io.File)*/OPEN,/*** Represents an "edit" action.* @see Desktop#edit(java.io.File)*/EDIT,/*** Represents a "print" action.* @see Desktop#print(java.io.File)*/PRINT,/*** Represents a "mail" action.* @see Desktop#mail()* @see Desktop#mail(java.net.URI)*/MAIL,/*** Represents a "browse" action.* @see Desktop#browse(java.net.URI)*/BROWSE};private DesktopPeer peer;/*** Suppresses default constructor for noninstantiability.*/private Desktop() {peer = Toolkit.getDefaultToolkit().createDesktopPeer(this);}/*** Returns the <code>Desktop</code> instance of the current* browser context.  On some platforms the Desktop API may not be* supported; use the {@link #isDesktopSupported} method to* determine if the current desktop is supported.* @return the Desktop instance of the current browser context* @throws HeadlessException if {@link* GraphicsEnvironment#isHeadless()} returns {@code true}* @throws UnsupportedOperationException if this class is not* supported on the current platform* @see #isDesktopSupported()* @see java.awt.GraphicsEnvironment#isHeadless*/public static synchronized Desktop getDesktop(){if (GraphicsEnvironment.isHeadless()) throw new HeadlessException();if (!Desktop.isDesktopSupported()) {throw new UnsupportedOperationException("Desktop API is not " +"supported on the current platform");}sun.awt.AppContext context = sun.awt.AppContext.getAppContext();Desktop desktop = (Desktop)context.get(Desktop.class);if (desktop == null) {desktop = new Desktop();context.put(Desktop.class, desktop);}return desktop;}
}

这里的 Desktop 就是使用的枚举类型的单例模式。

总结

综上所述,单例模式中,可以使用的为

  • 饿汉式(单线程,会造成内存的浪费)

  • 双重检验锁

  • 静态内部类

  • 枚举

使用情况:需要频繁创建销毁对象或者创建对象时耗时长、资源多的情况(如 session 工厂,工具类…),Runtime 在开发过程中,可能经常触发,大大减少了资源的占用。

注意情况:要想使用单例模式,必须使用提供的 get 方法,而不能去 new 对象,比如 Runtime 中使用 getRuntime 方法获取 Runtime 对象。

好处:节省资源,提高性能。

缺点:如果长时间不被使用,就会被回收,丢失状态数据;如果使用该对象的程序太多,会导致溢出,也不适合频繁变化的对象

参考资料

  • JAVA 设计模式

设计模式 – 工厂模式

本篇将介绍设计模式,包括简单工厂模式、工厂方法模式、抽象工厂模式三种,以及在什么情况下使用工厂模式。

简单工厂模式

比如我们有一个饭店,饭店的厨师做菜有以下几步 1、选择食材 2、切菜 3、炒菜 4、装盘 5、上菜

此时 2、3、4、5 无论什么菜都是一样的,我们这样写

Cook

package com.wangscaler.factory;/*** @author wangscaler* @date 2021.06.22 16:17*/
public abstract class Cook {protected String name;public abstract void prepare();public void cut() {System.out.println(name + "的材料被切好了");}public void fry() {System.out.println(name + "被炒好了");}public void dish() {System.out.println(name + "装进盘子里了");}public void serve() {System.out.println(name + "端上饭桌了");}public void setName(String name) {this.name = name;}
}

FishFlavoredPork

package com.wangscaler.factory;
/*** @author wangscaler* @date 2021.06.22 16:17*/
public class FishFlavoredPork extends Cook {@Overridepublic void prepare() {System.out.println("胡萝卜,肉丝,甜面酱等材料准备好了");}
}

KungPaoChicken

package com.wangscaler.factory;
/*** @author wangscaler* @date 2021.06.22 16:17*/
public class KungPaoChicken extends Cook {@Overridepublic void prepare() {System.out.println("鸡丁,黄瓜,花生等材料准备好了");}
}

Order

package com.wangscaler.factory;import java.io.BufferedReader;
import java.io.InputStreamReader;/*** @author wangscaler* @date 2021.06.22 16:17*/
public class Order {public Order() {Cook cook = null;String type;do {type = geType();if (type.equals(Menu.FISHFLAVOREDPORK.getName())) {cook = new FishFlavoredPork();cook.setName("鱼香肉丝");} else if (type.equals(Menu.KUNGPAOCHICKEN.getName())) {cook = new KungPaoChicken();cook.setName("宫保鸡丁");} else if (type.equals(Menu.EOF.getName())) {break;} else {break;}cook.prepare();cook.cut();cook.fry();cook.dish();cook.serve();} while (true);}public enum Menu {/*** 鱼香肉丝*/FISHFLAVOREDPORK("fishflavoredpork"),/*** 宫保鸡丁*/KUNGPAOCHICKEN("kungpaochicken"),/*** 结束标识*/EOF("EOF");private String name;Menu(String name) {this.name = name;}public String getName() {return name;}}private String geType() {try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));System.out.println("请输入您要点的菜,以EOF为结束");String str = bufferedReader.readLine();return str;} catch (Exception e) {e.printStackTrace();return "";}}
}

main

package com.wangscaler.factory;
/*** @author wangscaler* @date 2021.06.22 16:17*/
public class SimpleFactory {public static void main(String[] args) {Order order =new Order();}}

此时运行结果,一切正常

请输入您要点的菜,以EOF为结束
kungpaochicken
鸡丁,黄瓜,花生等材料准备好了
宫保鸡丁的材料被切好了
宫保鸡丁被炒好了
宫保鸡丁装进盘子里了
宫保鸡丁端上饭桌了
请输入您要点的菜,以EOF为结束
fishflavoredpork
胡萝卜,肉丝,甜面酱等材料准备好了
鱼香肉丝的材料被切好了
鱼香肉丝被炒好了
鱼香肉丝装进盘子里了
鱼香肉丝端上饭桌了
请输入您要点的菜,以EOF为结束
EOF

然而,当我们在菜单增加新的菜谱时,不仅要增加新的类,还要修改 Order 这个类,如果有很多的饭店,我们可能要写很多 Order 这样改起来不仅麻烦,还违反了我们设计原则中开闭原则的对扩展开放、对修改关闭。

修改 Order

package com.wangscaler.factory;import java.io.BufferedReader;
import java.io.InputStreamReader;/*** @author wangscaler* @date 2021.06.22 16:17*/
public class Order {//    public Order() {//        Cook cook = null;
//        String type;
//        do {//            type = geType();
//            if (type.equals(Menu.FISHFLAVOREDPORK.getName())) {//                cook = new FishFlavoredPork();
//                cook.setName("鱼香肉丝");
//            } else if (type.equals(Menu.KUNGPAOCHICKEN.getName())) {//                cook = new KungPaoChicken();
//                cook.setName("宫保鸡丁");
//            } else if (type.equals(Menu.EOF.getName())) {//                break;
//            } else {//                break;
//            }
//            cook.prepare();
//            cook.cut();
//            cook.fry();
//            cook.dish();
//            cook.serve();
//        } while (true);
//    }SimpleFactory simpleFactory;Cook cook = null;public Order(SimpleFactory simpleFactory) {setFactory(simpleFactory);}public void setFactory(SimpleFactory simpleFactory) {String type = "";this.simpleFactory = simpleFactory;do {type = geType();cook = this.simpleFactory.createCook(type);if (cook != null) {cook.prepare();cook.cut();cook.fry();cook.dish();cook.serve();} else {break;}} while (true);}private String geType() {try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));System.out.println("请输入您要点的菜,以EOF为结束");String str = bufferedReader.readLine();return str;} catch (Exception e) {e.printStackTrace();return "";}}
}

修改 SimpleFactory

package com.wangscaler.factory;/*** @author wangscaler* @date 2021.06.22 16:17*/
public class SimpleFactory {public Cook createCook(String type) {Cook cook = null;if (type.equals(SimpleFactory.Menu.FISHFLAVOREDPORK.getName())) {cook = new FishFlavoredPork();cook.setName("鱼香肉丝");} else if (type.equals(SimpleFactory.Menu.KUNGPAOCHICKEN.getName())) {cook = new KungPaoChicken();cook.setName("宫保鸡丁");}return cook;}public enum Menu {/*** 鱼香肉丝*/FISHFLAVOREDPORK("fishflavoredpork"),/*** 宫保鸡丁*/KUNGPAOCHICKEN("kungpaochicken"),/*** 结束标识*/EOF("EOF");private String name;Menu(String name) {this.name = name;}public String getName() {return name;}}public static void main(String[] args) {new Order(new SimpleFactory());}}

当我们新增加菜单时,只需要增加新的菜谱的类,和修改工厂,对 Order 没有影响。简单工厂模式也叫静态工厂模式,是因为我们的代码可以修改成

SimpleFactory 的 createCook 方法加上 static,之后 修改 Order

package com.wangscaler.factory;import java.io.BufferedReader;
import java.io.InputStreamReader;/*** @author wangscaler* @date 2021.06.22 16:17*/
public class Order1 {Cook cook = null;String type = "";public Order1() {do {type = geType();cook = SimpleFactory.createCook(type);if (cook != null) {cook.prepare();cook.cut();cook.fry();cook.dish();cook.serve();} else {break;}} while (true);}private String geType() {try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));System.out.println("请输入您要点的菜,以EOF为结束");String str = bufferedReader.readLine();return str;} catch (Exception e) {e.printStackTrace();return "";}}
}

由于所有的菜都是 SimpleFactory 来生产的,如果这个工厂出现问题,那么他生产出来的所有的菜也就跟着出了问题。而下面的这个模式,将对象的实例化延迟到子类。

工厂方法模式

比如我们现在不仅是有菜,还增加了味道,比如辣味、不辣三种味道的菜(能明白意思就行,不要计较能吃不能吃,哈哈),即辣味的鱼香肉丝和不辣的鱼香肉丝。

上述的 Cook 不修改,将鱼香肉丝变成辣味的 (SpicyFishFlavoredPork) 和不辣的(NotSpicyFishFlavoredPork),内容和之前的一样。然后我们修改了 Order 和增加两个工厂辣味工厂(SpicyFactory)和不辣工厂(NotSpicyFactory)。代码如下

Order

package com.wangscaler.factory.factorymethod;import java.io.BufferedReader;
import java.io.InputStreamReader;/*** @author wangscaler* @date 2021.06.22 16:17*/
public abstract class Order {Cook cook = null;String type = "";abstract Cook createCook(String type);public Order() {do {type = geType();cook = createCook(type);if (cook != null) {cook.prepare();cook.cut();cook.fry();cook.dish();cook.serve();} else {break;}} while (true);}private String geType() {try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));System.out.println("请输入您要点的菜,以EOF为结束");String str = bufferedReader.readLine();return str;} catch (Exception e) {e.printStackTrace();return "";}}
}

SpicyFactory

package com.wangscaler.factory.factorymethod;import com.wangscaler.factory.simplefactory.SimpleFactory;/*** @author wangscaler* @date 2021.06.23 10:10*/
public class SpicyFactory extends Order {@OverrideCook createCook(String type) {Cook cook = null;if (type.equals(SimpleFactory.Menu.FISHFLAVOREDPORK.getName())) {cook = new SpicyFishFlavoredPork();cook.setName("辣味鱼香肉丝");} else if (type.equals(SimpleFactory.Menu.KUNGPAOCHICKEN.getName())) {cook = new SpicyKungPaoChicken();cook.setName("辣味宫保鸡丁");}return cook;}public enum Menu {/*** 鱼香肉丝*/FISHFLAVOREDPORK("fishflavoredpork"),/*** 宫保鸡丁*/KUNGPAOCHICKEN("kungpaochicken"),/*** 结束标识*/EOF("EOF");private String name;Menu(String name) {this.name = name;}public String getName() {return name;}}
}

NotSpicyFactory

package com.wangscaler.factory.factorymethod;
import com.wangscaler.factory.simplefactory.SimpleFactory;/*** @author wangscaler* @date 2021.06.23 10:11*/
public class NotSpicyFactory extends Order {@OverrideCook createCook(String type) {Cook cook = null;if (type.equals(SimpleFactory.Menu.FISHFLAVOREDPORK.getName())) {cook = new NotSpicyFishFlavoredPork();cook.setName("鱼香肉丝");} else if (type.equals(SimpleFactory.Menu.KUNGPAOCHICKEN.getName())) {cook = new NotSpicyKungPaoChicken();cook.setName("宫保鸡丁");}return cook;}public enum Menu {/*** 鱼香肉丝*/FISHFLAVOREDPORK("fishflavoredpork"),/*** 宫保鸡丁*/KUNGPAOCHICKEN("kungpaochicken"),/*** 结束标识*/EOF("EOF");private String name;Menu(String name) {this.name = name;}public String getName() {return name;}}
}

我们可以看到我们在 Order 增加了抽象的方法 createCook,将他的实现去交给子类 NotSpicyFactory 和 SpicyFactory 去实现。

抽象工厂模式

抽象工厂模式就是简单工厂模式和工厂方法模式的结合。通俗的话说就是抽象工厂就是生产不同等级产品的工厂族,即既可以生产辣味工厂,又可以生产不辣工厂的工厂。

保持工厂方法模式中的 Cook、NotSpicyFishFlavoredPork、SpicyFishFlavoredPork 不变,添加抽象工厂 AbsFactory

package com.wangscaler.factory.abstractfactory;/*** @author wangscaler* @date 2021.06.23 11:00*/
public interface AbsFactory {public Cook createCook(String type);
}

提供接口,供辣味工厂和不辣工厂实现

SpicyFactory

package com.wangscaler.factory.abstractfactory;/*** @author wangscaler* @date 2021.06.23 10:10*/
public class SpicyFactory implements AbsFactory {@Overridepublic Cook createCook(String type) {Cook cook = null;if (type.equals(SpicyFactory.Menu.FISHFLAVOREDPORK.getName())) {cook = new SpicyFishFlavoredPork();cook.setName("辣味鱼香肉丝");} else if (type.equals(SpicyFactory.Menu.KUNGPAOCHICKEN.getName())) {cook = new SpicyKungPaoChicken();cook.setName("辣味宫保鸡丁");}return cook;}public enum Menu {/*** 鱼香肉丝*/FISHFLAVOREDPORK("fishflavoredpork"),/*** 宫保鸡丁*/KUNGPAOCHICKEN("kungpaochicken"),/*** 结束标识*/EOF("EOF");private String name;Menu(String name) {this.name = name;}public String getName() {return name;}}
}

NotSpicyFactory

package com.wangscaler.factory.abstractfactory;/*** @author wangscaler* @date 2021.06.23 10:10*/
public class NotSpicyFactory implements AbsFactory {@Overridepublic Cook createCook(String type) {Cook cook = null;if (type.equals(NotSpicyFactory.Menu.FISHFLAVOREDPORK.getName())) {cook = new NotSpicyFishFlavoredPork();cook.setName("鱼香肉丝");} else if (type.equals(NotSpicyFactory.Menu.KUNGPAOCHICKEN.getName())) {cook = new NotSpicyKungPaoChicken();cook.setName("宫保鸡丁");}return cook;}public enum Menu {/*** 鱼香肉丝*/FISHFLAVOREDPORK("fishflavoredpork"),/*** 宫保鸡丁*/KUNGPAOCHICKEN("kungpaochicken"),/*** 结束标识*/EOF("EOF");private String name;Menu(String name) {this.name = name;}public String getName() {return name;}}
}

此时我们的 Order 只需要使用抽象工厂即可

package com.wangscaler.factory.abstractfactory;import java.io.BufferedReader;
import java.io.InputStreamReader;/*** @author wangscaler* @date 2021.06.22 16:17*/
public class Order {AbsFactory factory;private void setFactory(AbsFactory factory) {Cook cook = null;String type = "";this.factory = factory;do {type = geType();cook = factory.createCook(type);if (cook != null) {cook.prepare();cook.cut();cook.fry();cook.dish();cook.serve();} else {break;}} while (true);}public Order(AbsFactory factory) {setFactory(factory);}private String geType() {try {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));System.out.println("请输入您要点的菜,以EOF为结束");String str = bufferedReader.readLine();return str;} catch (Exception e) {e.printStackTrace();return "";}}
}

main 函数

package com.wangscaler.factory.abstractfactory;/*** @author wangscaler* @date 2021.06.23 11:17*/
public class AbstractFactory {public static void main(String[] args) {new Order(new SpicyFactory());}}

这种方式具有良好的扩展性,比如我们增加微辣的口味,只需要添加微辣工厂去实现抽象工厂的 createCook 方法和添加微辣口味的菜。

源码中的工厂模式

JDK 中的 Calendar

Calendar calendar=Calendar.getInstance();我们打开 Calendar 的 getInstance 方法发现

/*** Gets a calendar using the default time zone and locale. The* <code>Calendar</code> returned is based on the current time* in the default time zone with the default* {@link Locale.Category#FORMAT FORMAT} locale.** @return a Calendar.*/public static Calendar getInstance(){return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));}/*** Gets a calendar using the specified time zone and default locale.* The <code>Calendar</code> returned is based on the current time* in the given time zone with the default* {@link Locale.Category#FORMAT FORMAT} locale.** @param zone the time zone to use* @return a Calendar.*/public static Calendar getInstance(TimeZone zone){return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));}/*** Gets a calendar using the default time zone and specified locale.* The <code>Calendar</code> returned is based on the current time* in the default time zone with the given locale.** @param aLocale the locale for the week data* @return a Calendar.*/public static Calendar getInstance(Locale aLocale){return createCalendar(TimeZone.getDefault(), aLocale);}/*** Gets a calendar with the specified time zone and locale.* The <code>Calendar</code> returned is based on the current time* in the given time zone with the given locale.** @param zone the time zone to use* @param aLocale the locale for the week data* @return a Calendar.*/public static Calendar getInstance(TimeZone zone,Locale aLocale){return createCalendar(zone, aLocale);}private static Calendar createCalendar(TimeZone zone,Locale aLocale){CalendarProvider provider =LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale).getCalendarProvider();if (provider != null) {try {return provider.getInstance(zone, aLocale);} catch (IllegalArgumentException iae) {// fall back to the default instantiation}}Calendar cal = null;if (aLocale.hasExtensions()) {String caltype = aLocale.getUnicodeLocaleType("ca");if (caltype != null) {switch (caltype) {case "buddhist":cal = new BuddhistCalendar(zone, aLocale);break;case "japanese":cal = new JapaneseImperialCalendar(zone, aLocale);break;case "gregory":cal = new GregorianCalendar(zone, aLocale);break;}}}if (cal == null) {// If no known calendar type is explicitly specified,// perform the traditional way to create a Calendar:// create a BuddhistCalendar for th_TH locale,// a JapaneseImperialCalendar for ja_JP_JP locale, or// a GregorianCalendar for any other locales.// NOTE: The language, country and variant strings are interned.if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {cal = new BuddhistCalendar(zone, aLocale);} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"&& aLocale.getCountry() == "JP") {cal = new JapaneseImperialCalendar(zone, aLocale);} else {cal = new GregorianCalendar(zone, aLocale);}}return cal;}

他其实调用的 createCalendar,而在 createCalendar 中我们发现

Calendar cal = null;

if (aLocale.hasExtensions()) {String caltype = aLocale.getUnicodeLocaleType("ca");if (caltype != null) {switch (caltype) {case "buddhist":cal = new BuddhistCalendar(zone, aLocale);break;case "japanese":cal = new JapaneseImperialCalendar(zone, aLocale);break;case "gregory":cal = new GregorianCalendar(zone, aLocale);break;}}}if (cal == null) {// If no known calendar type is explicitly specified,// perform the traditional way to create a Calendar:// create a BuddhistCalendar for th_TH locale,// a JapaneseImperialCalendar for ja_JP_JP locale, or// a GregorianCalendar for any other locales.// NOTE: The language, country and variant strings are interned.if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {cal = new BuddhistCalendar(zone, aLocale);} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"&& aLocale.getCountry() == "JP") {cal = new JapaneseImperialCalendar(zone, aLocale);} else {cal = new GregorianCalendar(zone, aLocale);}}return cal;

就是我们讲的简单工厂模式

总结

设计模式必须遵守设计原则:依赖抽象而不要依赖于具体,对扩展开放对修改关闭…

工厂模式的四个概念:

  • 抽象产品 (Cook): 是所有具体产品的父类。
  • 具体产品 (KungPaoChicken):继承了 Cook 的父类,也是工厂生产的对象实例。
  • 抽象工厂 (AbsFactory):提供创建对象接口,供子类实现。
  • 具体工厂 (SpicyFactory):实现抽象工厂的接口,生产对象实例。

以上三种设计模式可根据业务灵活使用

  • 简单工厂模式

    • 需要创建的对象较少
    • 客户端不需要关注对象创建的过程
  • 工厂方法模式
    • 不知道它所需要的对象的类
    • 通过其子类来指定创建哪个对象
    • 将创建对象的任务委托给多个工厂子类中的某一个
  • 抽象工厂模式
    • 需要一组或者多组对象完成一个功能
    • 不会频繁增加对象
    • 不知道需要的对象的类

参考资料

  • JAVA 设计模式
  • 深入理解工厂模式

设计模式 – 原型模式

原型模式其实就是一种克隆。用原型实例指定创建对象的种类,通过克隆这些原型,创建新的对象。原型模式有浅拷贝和深拷贝,快来看怎么实现原型模式吧。

原型模式

原型模式其实就是一种克隆。用原型实例指定创建对象的种类,通过克隆这些原型,创建新的对象。

起初我们有一只羊叫多利,颜色为白色,类型为绵羊,后来我们有钱了,需要引进更多属性和多利一样的羊我们怎么写?

Sheep

package com.wangscaler.prototype;/*** @author wangscaler* @date 2021.06.23 17:04*/
public class Sheep {private String name;private String color;private String type;public Sheep(String name, String color, String type) {this.name = name;this.color = color;this.type = type;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public String getType() {return type;}public void setType(String type) {this.type = type;}
}

main

package com.wangscaler.prototype;/*** @author wangscaler* @date 2021.06.23 17:04*/
public class Prototype {public static void main(String[] args) {Sheep sheep = new Sheep("多利", "白色", "绵羊");Sheep sheep1 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType());Sheep sheep2 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType());Sheep sheep3 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType());Sheep sheep4 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType());Sheep sheep5 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType());Sheep sheep6 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType());}
}

我们虽然克隆出了一摸一样的羊,但是我们不仅每次都重新初始化对象,还重新获取原始对象的属性,如果对象比较复杂,会大大影响我们的开发效率。我们发现 Object 类有个方法叫 clone,那么我们能不能通过实现 Cloneavle 接口的 clone 来达到我们的目的呢?

Sheep1

package com.wangscaler.prototype;/*** @author wangscaler* @date 2021.06.23 17:04*/
public class Sheep1 implements Cloneable {private String name;private String color;private String type;public Sheep1(String name, String color, String type) {this.name = name;this.color = color;this.type = type;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public String getType() {return type;}public void setType(String type) {this.type = type;}@Overrideprotected Object clone() throws CloneNotSupportedException {Sheep1 sheep = null;sheep = (Sheep1) super.clone();return sheep;}
}

main

package com.wangscaler.prototype;/*** @author wangscaler* @date 2021.06.23 17:04*/
public class Prototype {public static void main(String[] args) throws CloneNotSupportedException {Sheep1 sheep = new Sheep1("多利", "白色", "绵羊");Sheep1 sheep1 = (Sheep1) sheep.clone();Sheep1 sheep2 = (Sheep1) sheep.clone();Sheep1 sheep3 = (Sheep1) sheep.clone();Sheep1 sheep4 = (Sheep1) sheep.clone();Sheep1 sheep5 = (Sheep1) sheep.clone();Sheep1 sheep6 = (Sheep1) sheep.clone();}
}

我们同样复制出了属性相同的羊。

浅拷贝

  • 对于基本类型的成员变量,会进行值的传递
  • 对于引用类型的变量,会进行引用的传递,即内存地址的传递,此时复制前后的对象其实是同一个内存地址,修改其中一个,另一个也会跟着修改(原因可看我的 JAVA 易错点 1)
  • 上述克隆羊中的 clone 方法就是浅拷贝

深拷贝

  • 基本类型会产生值传递
  • 引用类型变量会申请存储空间,并复制引用类型变量所引用的对象

实现方式

  • 重写 clone 方法
  • 对象序列化

重写 clone 的方法

package com.wangscaler.prototype;import java.io.Serializable;/*** @author wangscaler* @date 2021.06.24 10:14*/
public class DeepClone implements Serializable,Cloneable {private static final long serialVersionID = 1L;private String name;private String aclass;public DeepClone(String name, String aclass) {this.name = name;this.aclass = aclass;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}

DeeProtoTypeClone

package com.wangscaler.prototype;import java.io.Serializable;/*** @author wangscaler* @date 2021.06.24 10:14*/
public class DeeProtoTypeClone implements Serializable, Cloneable {private String name;private DeepClone deepClone;public DeeProtoTypeClone() {super();}public String getName() {return name;}public void setName(String name) {this.name = name;}public DeepClone getDeepClone() {return deepClone;}public void setDeepClone(DeepClone deepClone) {this.deepClone = deepClone;}@Overridepublic String toString() {return "DeeProtoTypeClone{" +" + name + '\'' +", deepClone=" + deepClone +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {Object deep = null;deep = super.clone();DeeProtoTypeClone deeProtoTypeClone = (DeeProtoTypeClone) deep;deeProtoTypeClone.deepClone = (DeepClone) deepClone.clone();return deeProtoTypeClone;}
}

main

package com.wangscaler.prototype;/*** @author wangscaler* @date 2021.06.23 17:04*/
public class Prototype {public static void main(String[] args) throws CloneNotSupportedException {DeeProtoTypeClone deeProtoTypeClone = new DeeProtoTypeClone();deeProtoTypeClone.setName("乾隆");DeepClone deepClone = new DeepClone("纪晓岚", "清官");deeProtoTypeClone.setDeepClone(deepClone);DeeProtoTypeClone deeProtoTypeClone1 = (DeeProtoTypeClone) deeProtoTypeClone.clone();DeeProtoTypeClone deeProtoTypeClone2 = (DeeProtoTypeClone) deeProtoTypeClone.clone();System.out.println(deeProtoTypeClone.toString());System.out.println(deeProtoTypeClone1.toString());System.out.println(deeProtoTypeClone2.toString());}
}

执行结果

DeeProtoTypeClone{name='乾隆', deepClone=com.wangscaler.prototype.DeepClone@1540e19d}
DeeProtoTypeClone{name='乾隆', deepClone=com.wangscaler.prototype.DeepClone@677327b6}
DeeProtoTypeClone{name='乾隆', deepClone=com.wangscaler.prototype.DeepClone@14ae5a5}

我们可以发现和浅拷贝不同的是,我们成功的将 DeepClone 对象复制出了多个

对象序列化

DeeProtoTypeClone 添加序列化的方法

package com.wangscaler.prototype;import java.io.*;/*** @author wangscaler* @date 2021.06.24 10:14*/
public class DeeProtoTypeClone implements Serializable, Cloneable {private String name;private DeepClone deepClone;public DeeProtoTypeClone() {super();}public String getName() {return name;}public void setName(String name) {this.name = name;}public DeepClone getDeepClone() {return deepClone;}public void setDeepClone(DeepClone deepClone) {this.deepClone = deepClone;}@Overridepublic String toString() {return "DeeProtoTypeClone{" +" + name + '\'' +", deepClone=" + deepClone +'}';}//    @Override
//    protected Object clone() throws CloneNotSupportedException {//        Object deep = null;
//        deep = super.clone();
//        DeeProtoTypeClone deeProtoTypeClone = (DeeProtoTypeClone) deep;
//        deeProtoTypeClone.deepClone = (DeepClone) deepClone.clone();
//        return deeProtoTypeClone;
//    }public Object deepClone() {ObjectInputStream objectInputStream = null;ObjectOutputStream objectOutputStream = null;ByteArrayInputStream byteArrayInputStream = null;ByteArrayOutputStream byteArrayOutputStream = null;try {byteArrayOutputStream = new ByteArrayOutputStream();objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(this);byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());objectInputStream = new ObjectInputStream(byteArrayInputStream);DeeProtoTypeClone deeProtoTypeClone = (DeeProtoTypeClone) objectInputStream.readObject();return deeProtoTypeClone;} catch (Exception e) {e.printStackTrace();return null;} finally {try {objectInputStream.close();objectOutputStream.close();byteArrayInputStream.close();byteArrayOutputStream.close();} catch (Exception e1) {e1.printStackTrace();}}}
}

也是可以实现的,推荐使用第二种方式。

源码中的原型模式

spring 中 bean 的创建

<bean id="id01" class="com.wangscaler.bean.Person" scope="prototype"/>

我们给他指定为原型模式。当我们通过 getBean() 获取 bean 对象时,我们可以发现,创建出来的对象的属性是一样的。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object bean =applicationContext.getBean("id01");
Object bean1 =applicationContext.getBean("id01");
System.out.println("bean");
System.out.println("bean1");

当我们点进去getBean的源码,我们发现

public Object getBean(String name) throws BeansException {this.assertBeanFactoryActive();return this.getBeanFactory().getBean(name);
}

这里调用了 getBeanFactory 这个方法,点进去我们发现

private DefaultListableBeanFactory beanFactory;
public final ConfigurableListableBeanFactory getBeanFactory() {synchronized(this.beanFactoryMonitor) {if (this.beanFactory == null) {throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");} else {return this.beanFactory;}}
}

这里使用了 synchronized 同步来确保线程安全的返回这个工厂。

我们进到上一步的 getBean 方法里发现

public Object getBean(String name) throws BeansException {return this.doGetdBean(name, (Class)null, (Object[])null, false);
}

在这里调用了 doGetdBean,继续进入

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {String beanName = this.transformedBeanName(name);Object sharedInstance = this.getSingleton(beanName);if (mbd.isSingleton()) {sharedInstance = this.getSingleton(beanName, () -> {try {return this.createBean(beanName, mbd, args);} catch (BeansException var5) {this.destroySingleton(beanName);throw var5;}});bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);} else if (mbd.isPrototype()) {var11 = null;Object prototypeInstance;try {this.beforePrototypeCreation(beanName);prototypeInstance = this.createBean(beanName, mbd, args);} finally {this.afterPrototypeCreation(beanName);}bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);} 

在这里我们发现了先判断是否是单例模式 if (mbd.isSingleton())然后判断 else if (mbd.isPrototype()) 是否是原型模式,如果是的话,会调用 createBean 创建原型实例。

总结

使用原型模式,可以提高我们的效率,无需初始化对象,而且当对象的属性发生变化时,通过克隆也可以直接复制出变化后的对象。当对象比较复杂时或者我们不知道运行时对象的参数时,推荐使用原型模式。


设计模式 – 建造者模式

建造者模式

比如我们建造房子,建房子的过程有打桩、砌墙、封顶,房子的类型有平房、楼房、别墅

我们的代码这样写的

AbsHouse

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.24 13:56*/
public abstract class AbsHouse {public abstract void layingFoundation();public abstract void buildWall();public abstract void sealRoof();public void build() {layingFoundation();buildWall();sealRoof();}
}

Bungalow

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.24 13:56*/
public class Bungalow extends AbsHouse {@Overridepublic void layingFoundation() {System.out.println("平房筑基");}@Overridepublic void buildWall() {System.out.println("平房筑墙");}@Overridepublic void sealRoof() {System.out.println("平房封顶");}
}

main

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.24 13:56*/
public class Builder {public static void main(String[] args) {Bungalow bungalow = new Bungalow();bungalow.build();}
}

这种方式 ,耦合性较强,将房子的建造过程全部封装在一起,所以需要将房子和房子建造过程进行解耦

产品角色 House

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.23 14:46*/
public class House {private String foundation;private String wall;private String roof;public String getFoundation() {return foundation;}public void setFoundation(String foundation) {this.foundation = foundation;}public String getWall() {return wall;}public void setWall(String wall) {this.wall = wall;}public String getRoof() {return roof;}public void setRoof(String roof) {this.roof = roof;}
}

抽象的建造者 HouseBuilder

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.23 14:46*/
public abstract class HouseBuilder {protected House house = new House();public abstract void layingFoundation();public abstract void buildWall();public abstract void sealRoof();public House buildHouse() {return house;}
}

具体建造者 Bungalow

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.24 13:56*/
public class Bungalow extends HouseBuilder {@Overridepublic void layingFoundation() {System.out.println("平房筑基");}@Overridepublic void buildWall() {System.out.println("平房筑墙");}@Overridepublic void sealRoof() {System.out.println("平房封顶");}
}

以及另一个具体的建造者 Villa

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.24 14:49*/
public class Villa extends HouseBuilder {@Overridepublic void layingFoundation() {System.out.println("别墅筑基");}@Overridepublic void buildWall() {System.out.println("别墅筑墙");}@Overridepublic void sealRoof() {System.out.println("别墅封顶");}
}

指挥者

package com.wangscaler.builder;
/*** @author wangscaler* @date 2021.06.23 14:46*/
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.layingFoundation();houseBuilder.buildWall();houseBuilder.sealRoof();return houseBuilder.buildHouse();}
}

main

package com.wangscaler.builder;/*** @author wangscaler* @date 2021.06.24 13:56*/
public class Builder {public static void main(String[] args) {Villa villa = new Villa();HouseDirector houseDirector = new HouseDirector(villa);House house = houseDirector.constructHouse();Bungalow bungalow =new Bungalow();houseDirector.setHouseBuilder(bungalow);House house1 =houseDirector.constructHouse();}
}

当我们增加楼房时,只需要增加 TallBuilding 即可

package com.wangscaler.builder;
/*** @author wangscaler* @date 2021.06.24 13:56*/
public class TallBuilding extends HouseBuilder {@Overridepublic void layingFoundation() {System.out.println("楼房筑基");}@Overridepublic void buildWall() {System.out.println("楼房筑墙");}@Overridepublic void sealRoof() {System.out.println("楼房封顶");}
}

源码中的建造者模式

JDK 中的 StringBuilder

我们打开源码

package java.lang;public final class StringBuilderextends AbstractStringBuilderimplements java.io.Serializable, CharSequence
{/** use serialVersionUID for interoperability */static final long serialVersionUID = 4383685877147921099L;/*** Constructs a string builder with no characters in it and an* initial capacity of 16 characters.*/public StringBuilder() {super(16);}/*** Constructs a string builder with no characters in it and an* initial capacity specified by the {@code capacity} argument.** @param      capacity  the initial capacity.* @throws     NegativeArraySizeException  if the {@code capacity}*               argument is less than {@code 0}.*/public StringBuilder(int capacity) {super(capacity);}/*** Constructs a string builder initialized to the contents of the* specified string. The initial capacity of the string builder is* {@code 16} plus the length of the string argument.** @param   str   the initial contents of the buffer.*/public StringBuilder(String str) {super(str.length() + 16);append(str);}/*** Constructs a string builder that contains the same characters* as the specified {@code CharSequence}. The initial capacity of* the string builder is {@code 16} plus the length of the* {@code CharSequence} argument.** @param      seq   the sequence to copy.*/public StringBuilder(CharSequence seq) {this(seq.length() + 16);append(seq);}@Overridepublic StringBuilder append(Object obj) {return append(String.valueOf(obj));}@Overridepublic StringBuilder append(String str) {super.append(str);return this;}public StringBuilder append(StringBuffer sb) {super.append(sb);return this;}@Overridepublic StringBuilder append(CharSequence s) {super.append(s);return this;}/*** @throws     IndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder append(CharSequence s, int start, int end) {super.append(s, start, end);return this;}@Overridepublic StringBuilder append(char[] str) {super.append(str);return this;}/*** @throws IndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder append(char[] str, int offset, int len) {super.append(str, offset, len);return this;}@Overridepublic StringBuilder append(boolean b) {super.append(b);return this;}@Overridepublic StringBuilder append(char c) {super.append(c);return this;}@Overridepublic StringBuilder append(int i) {super.append(i);return this;}@Overridepublic StringBuilder append(long lng) {super.append(lng);return this;}@Overridepublic StringBuilder append(float f) {super.append(f);return this;}@Overridepublic StringBuilder append(double d) {super.append(d);return this;}/*** @since 1.5*/@Overridepublic StringBuilder appendCodePoint(int codePoint) {super.appendCodePoint(codePoint);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder delete(int start, int end) {super.delete(start, end);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder deleteCharAt(int index) {super.deleteCharAt(index);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder replace(int start, int end, String str) {super.replace(start, end, str);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int index, char[] str, int offset,int len){super.insert(index, str, offset, len);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, Object obj) {super.insert(offset, obj);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, String str) {super.insert(offset, str);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, char[] str) {super.insert(offset, str);return this;}/*** @throws IndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int dstOffset, CharSequence s) {super.insert(dstOffset, s);return this;}/*** @throws IndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int dstOffset, CharSequence s,int start, int end){super.insert(dstOffset, s, start, end);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, boolean b) {super.insert(offset, b);return this;}/*** @throws IndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, char c) {super.insert(offset, c);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, int i) {super.insert(offset, i);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, long l) {super.insert(offset, l);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, float f) {super.insert(offset, f);return this;}/*** @throws StringIndexOutOfBoundsException {@inheritDoc}*/@Overridepublic StringBuilder insert(int offset, double d) {super.insert(offset, d);return this;}@Overridepublic int indexOf(String str) {return super.indexOf(str);}@Overridepublic int indexOf(String str, int fromIndex) {return super.indexOf(str, fromIndex);}@Overridepublic int lastIndexOf(String str) {return super.lastIndexOf(str);}@Overridepublic int lastIndexOf(String str, int fromIndex) {return super.lastIndexOf(str, fromIndex);}@Overridepublic StringBuilder reverse() {super.reverse();return this;}@Overridepublic String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);}private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {s.defaultWriteObject();s.writeInt(count);s.writeObject(value);}/*** readObject is called to restore the state of the StringBuffer from* a stream.*/private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();count = s.readInt();value = (char[]) s.readObject();}}

发现他继承了 AbstractStringBuilder

abstract class AbstractStringBuilder implements Appendable, CharSequence {/*** The value is used for character storage.*/char[] value;/*** The count is the number of characters used.*/int count;/*** This no-arg constructor is necessary for serialization of subclasses.*/AbstractStringBuilder() {}/*** Creates an AbstractStringBuilder of the specified capacity.*/AbstractStringBuilder(int capacity) {value = new char[capacity];}public AbstractStringBuilder append(Object obj) {return append(String.valueOf(obj));}public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;}// Documentation in subclasses because of synchro differencepublic AbstractStringBuilder append(StringBuffer sb) {if (sb == null)return appendNull();int len = sb.length();ensureCapacityInternal(count + len);sb.getChars(0, len, value, count);count += len;return this;}/*** @since 1.8*/AbstractStringBuilder append(AbstractStringBuilder asb) {if (asb == null)return appendNull();int len = asb.length();ensureCapacityInternal(count + len);asb.getChars(0, len, value, count);count += len;return this;}// Documentation in subclasses because of synchro difference@Overridepublic AbstractStringBuilder append(CharSequence s) {if (s == null)return appendNull();if (s instanceof String)return this.append((String)s);if (s instanceof AbstractStringBuilder)return this.append((AbstractStringBuilder)s);return this.append(s, 0, s.length());}private AbstractStringBuilder appendNull() {int c = count;ensureCapacityInternal(c + 4);final char[] value = this.value;value[c++] = 'n';value[c++] = 'u';value[c++] = 'l';value[c++] = 'l';count = c;return this;}@Overridepublic AbstractStringBuilder append(CharSequence s, int start, int end) {if (s == null)s = "null";if ((start < 0) || (start > end) || (end > s.length()))throw new IndexOutOfBoundsException("start " + start + ", end " + end + ", s.length() "+ s.length());int len = end - start;ensureCapacityInternal(count + len);for (int i = start, j = count; i < end; i++, j++)value[j] = s.charAt(i);count += len;return this;}public AbstractStringBuilder append(char[] str) {int len = str.length;ensureCapacityInternal(count + len);System.arraycopy(str, 0, value, count, len);count += len;return this;}public AbstractStringBuilder append(char str[], int offset, int len) {if (len > 0)                // let arraycopy report AIOOBE for len < 0ensureCapacityInternal(count + len);System.arraycopy(str, offset, value, count, len);count += len;return this;}public AbstractStringBuilder append(boolean b) {if (b) {ensureCapacityInternal(count + 4);value[count++] = 't';value[count++] = 'r';value[count++] = 'u';value[count++] = 'e';} else {ensureCapacityInternal(count + 5);value[count++] = 'f';value[count++] = 'a';value[count++] = 'l';value[count++] = 's';value[count++] = 'e';}return this;}@Overridepublic AbstractStringBuilder append(char c) {ensureCapacityInternal(count + 1);value[count++] = c;return this;}public AbstractStringBuilder append(int i) {if (i == Integer.MIN_VALUE) {append("-2147483648");return this;}int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1: Integer.stringSize(i);int spaceNeeded = count + appendedLength;ensureCapacityInternal(spaceNeeded);Integer.getChars(i, spaceNeeded, value);count = spaceNeeded;return this;}public AbstractStringBuilder append(long l) {if (l == Long.MIN_VALUE) {append("-9223372036854775808");return this;}int appendedLength = (l < 0) ? Long.stringSize(-l) + 1: Long.stringSize(l);int spaceNeeded = count + appendedLength;ensureCapacityInternal(spaceNeeded);Long.getChars(l, spaceNeeded, value);count = spaceNeeded;return this;}public AbstractStringBuilder append(float f) {FloatingDecimal.appendTo(f,this);return this;}public AbstractStringBuilder append(double d) {FloatingDecimal.appendTo(d,this);return this;}}

以 append 方法为例,我们发现他是实现的 Appendable 这个抽象类

/** Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.**/package java.lang;import java.io.IOException;public interface Appendable {Appendable append(CharSequence csq) throws IOException;Appendable append(CharSequence csq, int start, int end) throws IOException;Appendable append(char c) throws IOException;
}

这个 Appendable 就可以说是我们建造者模式的抽象建造者,在这里创建了抽象方法,那么我们的 AbstractStringBuilder 就是具体建造者实现了这些抽象方法,StringBuilder 既充当了指挥者角色

Override
public StringBuilder append(Object obj) {return append(String.valueOf(obj));
}

又充当了具体的建造者

@Override
public StringBuilder append(String str) {super.append(str);return this;
}

总结

建造者四种角色

  • 产品角色:包含多个组成部件 (筑基、筑墙、封顶) 的一个具体的对象(房子)
  • 抽象建造者:包含创建产品各个部件(筑基、筑墙、封顶)的抽象方法的接口,一般还包含返回最终产品的方法(buildHouse)。
  • 具体建造者:实现接口,实现构建装配各个部件的方法(筑基、筑墙、封顶)
  • 指挥者:调用构建部件的方法完成对象的创建(组装房子)。

使用场景:

1、建造者创建的产品一般具有较多的共同点(盖楼都是筑基、筑墙、封顶),如果差异比较大,则不适合使用。

2、创建简单的对象一般使用工厂模式,创建复杂对象(多于 5 个组件)一般使用建造者模式

3、相同的方法,不同的执行顺序,产生不同的结果。

4、 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。

5、产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。

6、初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

参考资料

  • JAVA 设计模式
  • 建造者模式(Bulider 模式)详解

设计模式 – 适配器模式

适配器模式 适配器模式就是将某个类的接口转换成客户端期望的另一个接口,比如我们的耳机都是圆的,而手机只有方的充电口,那么我们可以使用转接头转换,转接就是适配器。

适配器模式

适配器模式就是将某个类的接口转换成客户端期望的另一个接口,比如我们的耳机都是圆的,而手机只有方的充电口,那么我们怎么使用耳机?这时候转接头就是适配器,将方口的接口转换成圆口的接口。

类适配器

比如我们的手机的耳机口是方口的

Phone

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:06*/
public class Phone {public String rectanglePort() {String src = "方口";System.out.println(src + "的耳机口");return src;}}

而我们的耳机需要的是圆口的, 我们有一个圆形的接口

RoundPort

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:06*/
public interface RoundPort {public String roundPort();
}

所以我们的适配器

PhoneAdapter

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:06*/
public class PhoneAdapter extends Phone implements RoundPort {@Overridepublic String roundPort() {String src = rectanglePort();if (src.equals("方口")) {//复杂的转换过程之后src = "圆口";}return src;}
}

我们的耳机有一个听歌的功能,此时像实现听歌,就必须依赖圆口

HeadPhone

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:06*/
public class HeadPhone {public void listen(RoundPort roundPort) {if (roundPort.roundPort().equals("圆口")) {System.out.println("正在听歌");} else {System.out.println("无法连通");}}
}

main

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:06*/
public class Adapter {public static void main(String[] args) {HeadPhone headPhone = new HeadPhone();headPhone.listen(new PhoneAdapter());}
}

此时,我们实现这个适配器必须继承 Phone 来使用 rectanglePort 这个方法,此时 Phone 的所有的方法都将暴露给 Adapter, 增加了使用成本,又因为 Java 是单继承,所以 RoundPort 必须是接口。

对象适配器

根据 “合成复用原则”,将继承关系转换成关联关系。对象适配器也是最常用的适配器模式。

所以只需要修改 PhoneAdapter 为

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:46*/
public class PhoneAdapter1 implements RoundPort {private Phone phone;public PhoneAdapter1(Phone phone) {this.phone = phone;}@Overridepublic String roundPort() {if (phone != null) {String src = phone.rectanglePort();if (src.equals("方口")) {src = "圆口";}return src;}return null;}
}

main

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:06*/
public class Adapter {public static void main(String[] args) {HeadPhone headPhone = new HeadPhone();headPhone.listen(new PhoneAdapter1(new Phone()));}
}

这种方式不仅更加灵活,也可结成成本。

接口适配器

也称为缺省适配器模式,当不需要全部实现接口提供的方法时可以使用这个模式,不需要的方法没只需要实现空方法。

提供一个万能的适配器。

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.21 13:23*/
public interface UniversalAdapter {String roundPort();String rectanglePort();String squarePort();
}

让我们的适配器去默认实现空方法,待使用的时候重写

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:46*/
public class PhoneAdapter2 implements UniversalAdapter {private Phone phone;public PhoneAdapter2(Phone phone) {this.phone = phone;}@Overridepublic String roundPort() {return phone.rectanglePort();}@Overridepublic String rectanglePort() {return phone.rectanglePort();}@Overridepublic String squarePort() {return phone.rectanglePort();}
}

使用时重写

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 13:44*/
public class HeadPhone1 {public void listen() {PhoneAdapter2 phoneAdapter2 = new PhoneAdapter2(new Phone()) {@Overridepublic String roundPort() {if (super.roundPort().equals("方口")) {String src = "圆口";return src;}return null;}};if (phoneAdapter2.roundPort().equals("圆口")) {System.out.println("听音乐");} else {System.out.println("连通失败");}}
}

main

package com.wangscaler.adapter;/*** @author wangscaler* @date 2021.06.25 11:06*/
public class Adapter {public static void main(String[] args) {HeadPhone1 headPhone = new HeadPhone1();headPhone.listen();}
}

此种方式易于扩展,比如我们有方口的耳机,也可以直接重写 squarePort 来直接实现。这种方式在安卓的开发中是比较常见的。

源码中的建造者模式

SpringMVC 中的 HandlerAdapter

public class DispatcherServlet extends FrameworkServlet {java@Nullableprivate List<HandlerMapping> handlerMappings;@Nullableprivate List<HandlerAdapter> handlerAdapters;public DispatcherServlet() {this.setDispatchOptionsRequest(true);}public DispatcherServlet(WebApplicationContext webApplicationContext) {super(webApplicationContext);this.setDispatchOptionsRequest(true);}protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {Iterator var2 = this.handlerAdapters.iterator();while(var2.hasNext()) {HandlerAdapter adapter = (HandlerAdapter)var2.next();if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Object dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}

当我们发起请求的时候,比如 login 请求,就会经过 DispatcherServlet,在 doDispatch 中,根据mappedHandler = this.getHandler(processedRequest);,从请求中获取到 login 这个 handler。然后根据HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());他会去在 getHandlerAdapter 中迭代找到适合的适配器。

适配器 HandlerAdapter 源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package org.springframework.web.servlet;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;public interface HandlerAdapter {boolean supports(Object var1);@NullableModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;long getLastModified(HttpServletRequest var1, Object var2);
}

从下图可以看到,有 6 个类实现了我们的接口

然后通过执行 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());来执行我们的 Handler, 执行完我们的业务接口之后,会拿到我们返回的 ModelAndView,然后去寻找对应的 view 资源,进行返回。

在这里 DispatcherServlet 作为用户,HandlerAdapter 就是我们期望接口

总结

适配器不是在详细设计时添加的,而是解决正在服役的项目的问题,现在的接口不适合使用,可以利用适配器模式有动机地修改一个正常运行的系统的接口编程我们期待的接口。

使用适配器会增加系统的复杂性,增加代码阅读难度,过多使用适配器会使系统代码变得凌乱,所以尽量避免使用适配器。

核心就是 " 把一个类的接口变换成客户端所期待的另一种接口 "

参考资料

  • JAVA 设计模式

桥接模式将实现和抽象放在两个不同的类层次中,使两个层次独立改变,即抽象化与实现化解耦,使得二者可以独立变化。

设计模式 – 桥接模式

将实现和抽象放在两个不同的类层次中,使两个层次独立改变,即抽象化与实现化解耦,使得二者可以独立变化

假如我们现在的交通工具有汽车和火车,动力提供的方式有电和油。桥接模式如下

提供 VehicleType 接口

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/
public interface VehicleType {void run();
}

供 Car 去实现

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/
public class Car implements VehicleType {@Overridepublic void run() {System.out.println("汽车在行驶");}
}

供 Train 去实现

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/
public class Train implements VehicleType {@Overridepublic void run() {System.out.println("火车在行驶");}
}

中间的桥梁即抽象类 Vehicle

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/
public abstract class Vehicle {private VehicleType vehicleType;public Vehicle(VehicleType vehicleType) {this.vehicleType = vehicleType;}protected void run() {this.vehicleType.run();}
}

电力的 ElectricVehicle 通过继承 Vehicle 的桥梁获取接口的实现类里的super.run();

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/
public class ElectricVehicle extends Vehicle {public ElectricVehicle(VehicleType vehicleType) {super(vehicleType);}@Overrideprotected void run() {System.out.println("提供电");super.run();}
}

同理油力的 OilVehicle 也是通过继承 Vehicle 的桥梁获取接口的实现类里的super.run();

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/
public class OilVehicle extends Vehicle {public OilVehicle(VehicleType vehicleType) {super(vehicleType);}@Overrideprotected void run() {System.out.println("提供油");super.run();}
}

main

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/public class Bridge {public static void main(String[] args) {ElectricVehicle electricVehicle = new ElectricVehicle(new Train());electricVehicle.run();}
}

当我们增加了油电混合的能源时,只需要增加 OilElectricVehicle

package com.wangscaler.bridge;/*** @author wangscaler* @date 2021.06.24 16:48*/
public class OilElectricVehicle extends Vehicle {public OilElectricVehicle(VehicleType vehicleType) {super(vehicleType);}@Overrideprotected void run() {System.out.println("油电混合的能源");super.run();}
}

即可实现。

源码中的桥接模式

JDBC 中 Driver 接口

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package com.mysql.cj.jdbc;import java.sql.DriverManager;
import java.sql.SQLException;public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}

java.sql.Driver 就是桥接模式中的接口,上述代码是 Mysql 的 Driver 也就是 Driver 的实现类,当然 Orcale 的 Driver 也是实现的这个接口

而这个实现类通过 DriverManager 的 registerDriver 来注册驱动, 此时将 Driver 加入到 registeredDrivers 中。

当我们加载了 Mysql 的驱动,通过 DriverManager 在调用 getConnection 的时候,会根据使用的驱动加载 connection

public class DriverManager {private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();public static synchronized void registerDriver(java.sql.Driver driver)throws SQLException {registerDriver(driver, null);}@CallerSensitivepublic static Connection getConnection(String url,java.util.Properties info) throws SQLException {return (getConnection(url, info, Reflection.getCallerClass()));}public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)throws SQLException {/* Register the driver if it has not already been added to our list */if(driver != null) {registeredDrivers.addIfAbsent(new DriverInfo(driver, da));} else {// This is for compatibility with the original DriverManagerthrow new NullPointerException();}println("registerDriver: " + driver);}@CallerSensitivepublic static Connection getConnection(String url,String user, String password) throws SQLException {java.util.Properties info = new java.util.Properties();if (user != null) {info.put("user", user);}if (password != null) {info.put("password", password);}return (getConnection(url, info, Reflection.getCallerClass()));}@CallerSensitivepublic static Connection getConnection(String url)throws SQLException {java.util.Properties info = new java.util.Properties();return (getConnection(url, info, Reflection.getCallerClass()));}private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {/** When callerCl is null, we should check the application's* (which is invoking this class indirectly)* classloader, so that the JDBC driver class outside rt.jar* can be loaded from here.*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {callerCL = Thread.currentThread().getContextClassLoader();}}if(url == null) {throw new SQLException("The url cannot be null", "08001");}println("DriverManager.getConnection(\"" + url + "\")");// Walk through the loaded registeredDrivers attempting to make a connection.// Remember the first exception that gets raised so we can reraise it.SQLException reason = null;for(DriverInfo aDriver : registeredDrivers) {// If the caller does not have permission to load the driver then// skip it.if(isDriverAllowed(aDriver.driver, callerCL)) {try {println("    trying " + aDriver.driver.getClass().getName());Connection con = aDriver.driver.connect(url, info);if (con != null) {// Success!println("getConnection returning " + aDriver.driver.getClass().getName());return (con);}} catch (SQLException ex) {if (reason == null) {reason = ex;}}} else {println("    skipping: " + aDriver.getClass().getName());}}// if we got here nobody could connect.if (reason != null)    {println("getConnection failed: " + reason);throw reason;}println("getConnection: no suitable driver found for "+ url);throw new SQLException("No suitable driver found for "+ url, "08001");}}

比如 Mysql 的 Driver 就是继承了 NonRegisteringDriver 并实现了 connect 这个方法

public class NonRegisteringDriver implements Driver {public Connection connect(String url, Properties info) throws SQLException {try {try {if (!ConnectionUrl.acceptsUrl(url)) {return null;} else {ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);switch(conStr.getType()) {case SINGLE_CONNECTION:return ConnectionImpl.getInstance(conStr.getMainHost());case FAILOVER_CONNECTION:case FAILOVER_DNS_SRV_CONNECTION:return FailoverConnectionProxy.createProxyInstance(conStr);case LOADBALANCE_CONNECTION:case LOADBALANCE_DNS_SRV_CONNECTION:return LoadBalancedConnectionProxy.createProxyInstance(conStr);case REPLICATION_CONNECTION:case REPLICATION_DNS_SRV_CONNECTION:return ReplicationConnectionProxy.createProxyInstance(conStr);default:return null;}}} catch (UnsupportedConnectionStringException var5) {return null;} catch (CJException var6) {throw (UnableToConnectException)ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);}} catch (CJException var7) {throw SQLExceptionsMapping.translateException(var7);}}}

从而真正的实现了我们使用 Mysql 驱动时,调用 getConnection 是获取的 mysql 的连接。

总结

1、桥接模式最大的特点就是抽象和实现进行分离,大大增加了系统的灵活性,从而产生结构化系统。

2、适用于那些不希望继承或多层次继承来导致系统类的个数急剧增加的系统;一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。

3、扩展任一维度,只需要增加不需要修改。符合开闭原则。

参考资料

  • [JAVA 设计模式](

一口气讲完设计模式(单例模式、工厂模式、原型模式、建造者模式、适配器、桥梁模式)相关推荐

  1. 设计模式(一):单例、工厂、原型、建造者、适配器

    前言:本文为原创 若有错误欢迎评论! 一.UML基本介绍 1.概念: 统一建模语言 用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系 2.类之间的关系: 依赖(所有关系的本质):1) ...

  2. 设计模式之创建型模式(工厂、原型、建造者)

    文章目录 创建型模式 2.1 工厂设计模式 2.1.1 简单工厂模式 2.1.2 工厂方法模式 2.1.3 抽象工厂 2.1.4 工厂模式总结 2.1.5 Spring中的工厂模式 2.1.6 工作中 ...

  3. 常见的设计模式(单例模式工厂模式)

    目录 一.为什么要学习设计模式? 二.单例模式 概念 优点 缺点 1.饿汉模式 1.1概念 1.2示例 2.懒汉模式 2.1 概念 2.2 示例 三.工厂模式 1.概念 2.使用场景 3.工厂方法 一 ...

  4. 一口气说完MR、Storm、Spark、SparkStreaming和Flink

    这是彭文华的第92篇原创 一直想写一篇大数据计算引擎的综述,但是这个话题有点大.今天试试看能不能一口气写完.没想到一口气从7点写到了凌晨2点 大数据计算的起点是Hadoop的MapReduce.之前虽 ...

  5. [置顶]       设计模式之结构类模式——桥梁模式

    桥梁模式(Bridge Patter)也叫做桥接模式,是一个比较简单的模式. 定义: 将抽象和实现解耦,使得两者可以独立地变化. 通用类图: ● Abstraction--抽象化角色 它主要的职责是定 ...

  6. Android中的设计模式-桥梁模式

    "假舆马者,非利足也,而致千里:假舟楫者,非能水也,而绝江河.君子生非异也,善假于物也."--荀子<劝学>. 美国好莱坞电影有<蜘蛛侠>.<蝙蝠侠&g ...

  7. java设计模式-桥梁模式

    桥梁模式 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是"将抽象化(Abstraction)与实现化(Imple ...

  8. 23种设计模式-桥梁模式《官渡之战》

    对于许久不用的东西,容易忘记.百度许久,也未能找到自己所要. 从今日起,有些东西就记载下来,不仅方便自己,希望能帮到他人吧! 定义: 结构型模式一种 .设计程序过程中 , 会经常使用到抽象类或者接口来 ...

  9. 设计模式(八)桥梁模式(Bridge)

    一.写在前面 之前有读者评论说,前边的文章开头理论性太强了,显得晦涩难懂,会把读者搞晕,谢谢这位读者,同时也希望其他的读者多提意见,帮助我改正提高博客,为了改进之前的问题,今天我们先用例子引入,然后再 ...

最新文章

  1. 我用24小时、8块GPU、400美元在云上完成训练BERT!特拉维夫大学新研究
  2. windows7下载python教程-Windows 7下Python Web环境搭建图文教程
  3. 思科服务器与交换机链接配置文件,使用思科S系列交换机上的配置迁移工具转换配置文件...
  4. java 文件上传 servlet_java文件上传-原始的Servlet方式
  5. 前端学习(2569):如何跨组件调用实例
  6. V210 SPI驱动分析
  7. 45 CO配置-控制-利润中心会计-维护控制范围设置
  8. java中lock_Java中的锁
  9. 解决能上QQ不能上网页的批处理〖罗斌原创〗
  10. Android杂谈--ListView之SimpleAdapter的使用
  11. 金蝶站点重新输入服务器ip,金蝶KIS客户端和服务器不在同一IP段互相访问
  12. 我应关注的AEC算法细分
  13. 小D课堂 - 零基础入门SpringBoot2.X到实战_第10节 SpringBoot整合定时任务和异步任务处理_41、SpringBoot定时任务schedule讲解...
  14. 整理了上千个Python类库,简直太酷啦!
  15. 蓝桥杯练习题之数列特征
  16. 小程序源码:王者荣耀改重复名,空白名最低战力查询助手-多玩法安装简单
  17. 综合布线中的配线架与理线架
  18. 日常报错:关于tomcat默认端口被占用的问题
  19. 网站获得高质量外链的两大绝招
  20. 该和thinkjs说bye bye了

热门文章

  1. 物联网让生活更加轻松的五种方式
  2. java-php-python-ssm-兴澜幼儿园管理系统-计算机毕业设计
  3. 架构师成长之路(3)--如何成为架构师(方法)
  4. 上门回收废品的app
  5. QString汉字个数检测
  6. Apache Maven 最全教程,7000 字总结!
  7. 使用Google学术自动生成标准的参考文献格式(Word版+LaTex版)
  8. 由frankmocap得到的.pkl文件转为.bvh或者.fbx
  9. logstash String转data,@timestamp转化为东八区时间
  10. 百家号同步公众号的自媒体工具有吗?