设计模式

什么是设计模式?

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。他不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

学习设计模式的意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

正确使用设计模式具有以下优点:

  • 可以提高程序员的思维能力、编程能力、和设计能力。
  • 使用设计更加标准化,代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  • 使代码的可重用性高、可读性强、可靠性高、灵活性号、可维护性强。

GoF23

GoF23

  • 一种思维、一种态度、一种进步

创建型模式:

  • 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式

结构型模式:

  • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

行为型模式:

  • 模仿方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

设计模式的基本要素

模式名称

问题

解决方法

效果

OOP七大原则

开闭原则:对扩展开放,对修改关闭

里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立

依赖倒置原则:要面向接口编程,不要面向实现编程

单一职责原则:控制类的粒度大小,将对象解耦、提高其内聚性

接口隔离原则:要为各个类建立它们需要的专用接口

迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话

合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

单例模式

Ensure a class has only one instance, and provide a global point of access to it.

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例

工厂模式


作用

  • 实现了创建者和调用者分离
  • 详细分类:
    • 简单工厂模式
    • 工厂方法模式
    • 抽象工厂模式

OOP七大原则

  • 开闭原则:对扩展开放,对修改关闭
  • 依赖倒置原则:要面向接口编程,不要面向实现编程
  • 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话

核心本质:

  • 实例化对象不适用new,用工厂方法代替
  • 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。

三种模式:

  • 简单工厂模式
    • 用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
  • 工厂方法模式
    • 用来生产同一等级结构中的固定产品(支持增加任意产品)
  • 抽象工厂模式
    • 围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂

简单工厂模式

1
2
3
4
5
6
package UML.factory.simple;

public interface Car {
void name();
}

1
2
3
4
5
6
7
8
package UML.factory.simple;

public class WuLing implements Car{
@Override
public void name() {
System.out.println("五菱宏光");
}
}
1
2
3
4
5
6
7
8
package UML.factory.simple;

public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉");
}
}
1
2
3
4
5
6
7
8
package UML.factory.simple;

public class Dazhong implements Car{
@Override
public void name() {
System.out.println("大众");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package UML.factory.simple;

// 静态工厂模式(简单工厂模式)
// 弊端:增加一个新的产品,如果不修改代码,做不到!
// 开闭原则
public class CarFactory {
// 方法一
public static Car getCar(String car){
if(car.equals("五菱")){
return new WuLing();
} else if (car.equals("特斯拉")) {
return new Tesla();
}else {
return null;
}
}
// 方法二
public static Car getWuLing(){
return new WuLing();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package UML.factory.simple;

public class Constumer {
public static void main(String[] args) {
//接口,所有的实现类
// Car car = new WuLing();
// Car car2 = new Tesla();

//2、使用工厂创建
Car car = CarFactory.getCar("五菱");
Car car2 = CarFactory.getCar("特斯拉");

car.name();
car2.name();
}
}

工厂方法模式

1
2
3
4
5
package UML.factory.method;

public interface Car {
void name();
}
1
2
3
4
5
6
7
8
package UML.factory.method;

public class WuLing implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
1
2
3
4
5
6
7
8
package UML.factory.method;

public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
1
2
3
4
5
6
7
8
9
package UML.factory.method;


public class MoBai implements Car {
@Override
public void name() {
System.out.println("摩拜单车");
}
}
1
2
3
4
5
6
package UML.factory.method;

// 工厂方法模式
public interface CarFactory {
Car getCar();
}
1
2
3
4
5
6
7
8
package UML.factory.method;

public class WuLingFactory implements CarFactory{
@Override
public Car getCar() {
return new WuLing();
}
}
1
2
3
4
5
6
7
8
package UML.factory.method;

public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
1
2
3
4
5
6
7
8
package UML.factory.method;

public class MoBaiFactory implements CarFactory{
@Override
public Car getCar() {
return new MoBai();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package UML.factory.method;

import UML.factory.simple.CarFactory;

public class Constumer {
public static void main(String[] args) {
Car car = new WuLingFactory().getCar();
Car car2 = new TeslaFactory().getCar();
Car car3 = new MoBaiFactory().getCar();

car.name();
car2.name();
car3.name();
}
// 结构复杂度: simple
// 代码复杂度: simple
// 编程复杂度: simple
// 管理上的复杂度:simple

// 根据设计原则:工厂方法模式
// 根据实际业务:简单工厂模式
}

小结:

  • 简单工厂模式(静态工厂模式)
    • 虽然某种程度上不符合设计原则,但实际使用最多!
  • 工厂方法模式
    • 不修改已有类的前提下,通过增加新的工厂类实现扩展
  • 抽象工厂模式
    • 不可以增加产品,可以增加产品族!

应用场景:

  • JDK中Calendar的getlnstance方法
  • JDBC中的Connection对象的获取
  • Spring中IOC容器创建管理bean对象
  • 反射中Class对象的newlnstance方法

抽象工厂模式

定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类

适用场景:

  • 客户端(应用层)不依赖产品类实例如何被创建、实现等细节

  • 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码

  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现

UML类图

抽象工厂

所有同一产品族里的东西都是由一个工厂生产的,但位于不同的等级结构

1
2
3
4
5
6
7
8
9
package UML.factory.abstract1;

// 手机品牌接口
public interface IphoneProduct {
void start();
void shutdown();
void callup();
void sendSMS();
}
1
2
3
4
5
6
7
8
9
package UML.factory.abstract1;

// 路由器产品接口
public interface IRouterProduct {
void start();
void shutdown();
void openwifi();
void setting();
}
1
2
3
4
5
6
7
8
9
10
11
package UML.factory.abstract1;

// 抽象产品工厂
public interface IProductFactory {
// 生产手机
IphoneProduct iphoneProduct();

// 生产路由器
IRouterProduct irouterProduct();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package UML.factory.abstract1;

// 小米手机
public class XiaomiPhone implements IphoneProduct{
@Override
public void start() {
System.out.println("开启小米手机");
}

@Override
public void shutdown() {
System.out.println("关闭小米手机");
}

@Override
public void callup() {
System.out.println("小米打电话");
}

@Override
public void sendSMS() {
System.out.println("小米发短信");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class XiaomiRouter implements IRouterProduct{
@Override
public void start() {
System.out.println("启动小米路由器");
}

@Override
public void shutdown() {
System.out.println("关闭小米路由器");
}

@Override
public void openwifi() {
System.out.println("打开小米WIFI");
}

@Override
public void setting() {
System.out.println("小米设置");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package UML.factory.abstract1;

// 华为手机
public class HuaweiPhone implements IphoneProduct{
@Override
public void start() {
System.out.println("开启华为手机");
}

@Override
public void shutdown() {
System.out.println("关闭华为手机");
}

@Override
public void callup() {
System.out.println("华为打电话");
}

@Override
public void sendSMS() {
System.out.println("华为发短信");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package UML.factory.abstract1;

public class HuaweiRouter implements IRouterProduct{
@Override
public void start() {
System.out.println("启动华为路由器");
}

@Override
public void shutdown() {
System.out.println("关闭华为路由器");
}

@Override
public void openwifi() {
System.out.println("打开华为WIFI");
}

@Override
public void setting() {
System.out.println("华为设置");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package UML.factory.abstract1;

public class XiaomiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct() {
return new XiaomiPhone();
}

@Override
public IRouterProduct irouterProduct() {
return new XiaomiRouter();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package UML.factory.abstract1;

public class HuaweiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct() {
return new HuaweiPhone();
}

@Override
public IRouterProduct irouterProduct() {
return new HuaweiRouter();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package UML.factory.abstract1;

public class Client {
public static void main(String[] args) {

System.out.println("=============小米系列产品=============");
// 小米工厂
XiaomiFactory xiaomiFactory = new XiaomiFactory();

IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct();
iphoneProduct.callup();
iphoneProduct.sendSMS();

IRouterProduct xiaomiProduct = xiaomiFactory.irouterProduct();
xiaomiProduct.openwifi();

System.out.println("=============华为系列产品=============");
// 华为工厂
HuaweiFactory huaweiFactory = new HuaweiFactory();

IphoneProduct iphoneProduct1 = huaweiFactory.iphoneProduct();
iphoneProduct1.callup();
iphoneProduct1.sendSMS();

IRouterProduct huaweiProduct = huaweiFactory.irouterProduct();
huaweiProduct.openwifi();
}
}

适配器模式

结构型模式

作用:

  • 从程序结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题

分类:

  • 适配器模式
  • 代理模式
  • 桥接模式
  • 装饰模式
  • 组合模式
  • 外观模式
  • 享元模式

适配器模式

类比:

  • 类比
1
2
3
4
5
6
7
8
9
package UML.adapter;

//要被适配的类:网线
public class Adaptee {

public void request(){
System.out.println("连接网线上网");
}
}
1
2
3
4
5
6
7
8
9
package UML.adapter;

// 接口转换器的抽象实现
public interface Net2Usb {

// 作用:处理请求,网线=>usb
public void handleRequest();
}

1
2
3
4
5
6
7
8
9
10
11
package UML.adapter;
// 1、继承(类适配器,单继承)

// 真正的适配器,需要连接USB,连接网线
public class Adapter extends Adaptee implements Net2Usb{
@Override
public void handleRequest() {
super.request(); // 可以上网了
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package UML.adapter;

// 2、组合(对象适配器,常用)

// 真正的适配器,需要连接USB,连接网线
public class Adapter2 implements Net2Usb{
private Adaptee adaptee;

public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}

@Override
public void handleRequest() {
adaptee.request(); // 可以上网了
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package UML.adapter;

// 客户端类:想上网,插不上网线
public class Computer {

// 我们的电脑需要连接上转接器才能上网
public void net(Net2Usb adapter){
// 上网的具体实现,找一个转接头
adapter.handleRequest();
}

public static void main(String[] args) {
// 电脑、适配器、网线
Computer computer = new Computer(); // 电脑
Adaptee adaptee = new Adaptee(); // 网线
Adapter adapter = new Adapter(); // 转接器(类似无线网卡,本身自带上网功能)
Adapter2 adapter2 = new Adapter2(adaptee); // 转接器(需插入网线)

computer.net(adapter);
computer.net(adapter2);
}
}

将一个类的接口转换为客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作!

角色分析:

  • 目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口
  • 需要适配的类:需要适配的类或适配者类
  • 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象!

分析


对象适配器优点:

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标
  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”适配者的子类也可以通过该适配器进行适配

类适配器的缺点:

  • 对于Java、C#等不支持多继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
  • 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性

适用场景:

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码
  • 想创建一个可以重复使用的类,用于一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

桥接模式

桥接模式bridge

桥接模式是将抽象部分实现部分分离,使他们都可以独立地变化。它是一种对象结构模式,又称为柄体(Handle and Body)模式或接口(Interface)模式

  • 举例

  • 多继承:台式电脑继承电脑,联想台式继承台式电脑……(以此类推)

    • 当我需要增加一个品牌或设备时,要增加至少三个类(例如增加“华为品牌”则需增加“华为台式”、“华为笔记本”、“华为平板”;或增加“一体机电脑”则需增加“联想一体机”、“苹果一体机”、“戴尔一体机”)
    • 违反了单一职责原则:例如“联想台式”这个类中既有品牌“联想”又有类型“台式”

  • 分析:在以上场景中有两个变化的维度:品牌、类型

  • 分析

  • 现在我们将“类型”与“品牌”独立出来,想办法通过一个“桥”把“类型”和“品牌”连接起来,使得在“类型”的内部可以去引用“品牌”从而实现功能组合(可以理解为当我们买到一台电脑时这台电脑肯定是有一个“品牌的”)

  • 采用这种设计方案会使得系统中的类的个数会大大减少,并且系统扩展更加方便
  • 桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合
  • 桥接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package UML.bridge;

// 品牌
public interface Brand {
void info();
}

class Apple implements Brand{
@Override
public void info() {
System.out.print("苹果");
}
}

class Lenovo implements Brand{
@Override
public void info() {
System.out.print("联想");
}
}

class Dell implements Brand{
@Override
public void info() {
System.out.print("戴尔");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package UML.bridge;

// 抽象的电脑类型
// 这里不用接口,因为电脑出厂自带品牌,要将品牌和电脑组合起来,而不要去继承
public abstract class Computer {

// 组合,品牌 —— 桥
private Brand brand;

public Computer(Brand brand) {
this.brand = brand;
}

public void info(){
brand.info();// 自带品牌
}
}

class Desktop extends Computer{

public Desktop(Brand brand) {
super(brand);
}

@Override
public void info() {
super.info();
System.out.println("台式机");
}
}

class Laptop extends Computer{

public Laptop(Brand brand) {
super(brand);
}

@Override
public void info() {
super.info();
System.out.println("笔记本");
}
}

class Tablet extends Computer{

public Tablet(Brand brand) {
super(brand);
}

@Override
public void info() {
super.info();
System.out.println("平板电脑");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package UML.bridge;

public class Test {
public static void main(String[] args) {
// 苹果笔记本
Computer computer1 = new Laptop(new Apple());
computer1.info();
// 联想笔记本
Computer computer2 = new Desktop(new Lenovo());
computer2.info();
}
}

类图


好处分析:

  • 桥接模式偶尔类似于多继承方案,但多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大地减少了子类的个数,从而降低管理和维护的成本。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!

劣势分析:

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者对抽象进行设计和编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。(耦合性过强时无法使用)

最佳实践:

  • 如果一个系统需要在构建抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合、
  • 一个类存在两个独立变化的维度,且这两个维度都需要扩展
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。