设计模式
2024-01-04 07:27:35

创建者模式

单例模式

**单例模式(Singleton Pattern)**是 Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的主要有以下角色

  • 单例类 只能创建一个实例的类
  • 访问类 使用单例类

单例设计模式分类两种

  • 饿汉式 类加载就会导致该单实例对象被创建
  • 懒汉式 类加载不会导致该单实例对象被创建,而是首次使用逐对象时才会创建

饿汉式

  • 静态成员方法

    创建单例类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Singleton {
    /**
    * 构造方法私有化
    */
    private Singleton(){}

    /**
    * 本类中创建本类对象
    */
    private static Singleton instance = new Singleton();

    /**
    * 提供一个公共方法,让外界获取该对象
    */
    public static Singleton getInstance(){
    return instance;
    }
    }

    测试

    1
    2
    3
    4
    5
    6
    @Test
    public void staticMemberMethod(){
    Singleton singleton1 = Singleton.getInstance();
    Singleton singleton2 = Singleton.getInstance();
    System.out.println( singleton1 == singleton2 );
    }

    image-20230126011130232

  • 静态代码块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Singleton {

    private Singleton(){}

    private static Singleton instance;

    /**
    * 静态代码块
    */
    static {
    instance = new Singleton();
    }
    public static Singleton getInstance(){
    return instance;
    }
    }
  • 枚举方式

    枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式

    1
    2
    3
    public enum Singleton {
    INSTANCE
    }

饿汉式创建单例对象都会随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费

懒汉式

  • 同步锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Singleton {
    private Singleton(){}

    private static Singleton instance;

    public static synchronized Singleton getInstance(){
    //判断instance是否为null,为null说明还没有创建Singleton类的对象
    if ( instance == null ) {
    //需要加锁,线程1进入if并等待,线程2获取到cpu实行权也会进入到if里面
    instance = new Singleton();
    }
    return instance;
    }
    }
  • 双重检查锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Singleton {
    private Singleton(){}

    private static Singleton instance;

    public static Singleton getInstance(){
    //判断instance是否为null,为null说明还没有创建Singleton类的对象
    if ( instance == null ) {
    synchronized ( Singleton.class ){
    if ( instance == null ) {
    //获取锁之后在次判断是否为null
    instance = new Singleton();
    }
    }
    }
    return instance;
    }
    }

    双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检查锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指问题,出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作。要解决双币检查锁模式带来空指针异常的问题,需要使用volaLile 关键字,voiaLile 关键字可以保证可见性和有序性

    1
    private static volatile Singleton instance;
  • 静态内部类

    静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Singleton {
    private Singleton(){}

    /**
    * 创建静态内部类
    */
    private static class SingletonHolder{
    private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
    }

    }

破坏单例模式

  1. 序列化
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
public class Client {

private final static String FILE_NAME = "E:\\code\\maven\\designPattern\\src\\main\\java\\com\\hg\\singleton\\demo5\\object";

public static void main( String[] args ) throws Exception {
readObjectFile();
readObjectFile();
}

/**
* 将实列写入文件中
*/
public static void writeObjectFile() throws Exception {
ObjectOutputStream outputStream = new ObjectOutputStream( Files.newOutputStream( Paths.get( FILE_NAME ) ) );
outputStream.writeObject( Singleton.getInstance() );
outputStream.close();
}

/**
* 读取序列化流
*/
public static void readObjectFile() throws Exception {
ObjectInputStream inputStream = new ObjectInputStream( Files.newInputStream( Paths.get( FILE_NAME ) ) );
Singleton instance = ( Singleton ) inputStream.readObject();
System.out.println( instance );
inputStream.close();
}

}

image-20230126031402427

  1. 反射
1
2
3
4
5
6
7
8
9
10
11
12
public static void reflex() throws Exception {
//获取 Singleton 的字节码文件
Class<Singleton> clazz = Singleton.class;
//获取无参构造方法
Constructor<Singleton> cons = clazz.getDeclaredConstructor();
//取消访问权限
cons.setAccessible( true );
//获取类的实列
Singleton singleton1 = cons.newInstance();
Singleton singleton2 = cons.newInstance();
System.out.println( singleton1 == singleton2 );
}

解决方法

  1. 反序列化

    当进行反序列化时,会自动调用 readResolve() 方法

1
2
3
dpublic Object readResolve(){
return SingletonHolder.INSTANCE;
}
  1. 反射
1
2
3
4
5
6
7
8
9
10
11
private static boolean flag = false;

private Singleton(){
//解决反射破坏单例模式
synchronized ( Singleton.class ){
if ( flag ){
throw new RuntimeException( "不能创建多个实列对象" );
}
flag = true;
}
}

工厂模式

设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖【LatteCoffee】)再设计一个咖啡店类(CoffeeStore)咖啡店具有点咖啡的功能

image-20230127012240016

咖啡类(Coffee

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Coffee {

public void addSugar(){
System.out.println("加糖");
}

public void addMilk(){
System.out.println("加牛奶");
}

public abstract void getName();
}

美式咖啡【AmericanCoffee】和拿铁咖【LatteCoffee

1
2
3
4
5
6
public class AmericanoCoffee extends Coffee {
@Override
public void getName() {
System.out.println( "美式咖啡" );
}
}
1
2
3
4
5
6
public class LatteCoffee extends Coffee {
@Override
public void getName() {
System.out.println( "拿铁咖啡" );
}
}

咖啡店类(CoffeeStore

1
2
3
4
5
6
7
8
9
10
11
public class CoffeeStore {
public Coffee orderCoffee( String type ) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
1
2
3
4
5
6
7
8
public static void main( String[] args ) {
CoffeeStore store = new CoffeeStore();
//获取指定咖啡
Coffee coffee = store.orderCoffee( "americano" );
coffee.getName();
coffee.addMilk();
coffee.addSugar();
}

Java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是**解耦**

简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯,简单工厂包含如下角色

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品 :实现或者继承抽象产品的子类
  • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品

**实现 : ** 使用简单工厂对上面案例进行改进:

image-20230127021216693

咖啡工厂

1
2
3
4
5
6
7
8
9
10
11
public class SimpleCoffeeFactory {
public Coffee createCoffee( String type ) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}

咖啡商店

1
2
3
4
5
6
7
8
public class CoffeeStore {
public Coffee orderCoffee( String type ) {
Coffee coffee = new SimpleCoffeeFactory().createCoffee( type );
//添加指定功能
coffee.addMilk();
return coffee;
}
}

工厂(factory)处理创建对象的细节,一旦有了 SimpleCoffeeFactoryCoffeeStore 类中的orderCoffee() 就变成此对象的客户,后期如果需要 Coffee 对象直接从工厂中获取即可。这样也就解除了和 Coffee 实现类的耦合,同时又产生了新的耦合,CoffeeStore 对象和 SimpleCoffeeFactory 工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的咖啡,势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

优点 : 封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点 : 增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

静态工厂

1
2
3
4
5
6
7
8
9
public static Coffee createCoffee( String type ) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}

工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类,工厂方法模式的主要角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应

image-20230127024700017

抽象工厂

1
2
3
public interface CoffeeFactory {
Coffee createCoffee();
}

具体工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//美式咖啡工厂
public class AmericanoCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new AmericanoCoffee();
}
}

//拿铁咖啡工厂
public class LatteCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}

咖啡店

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CoffeeStore {
private CoffeeFactory factory;

public CoffeeStore( CoffeeFactory factory ) {
this.factory = factory;
}

public Coffee orderCoffee() {
Coffee coffee = factory.createCoffee();
coffee.addMilk();
return coffee;
}
}

工厂方法模式要增加产品类时要相应地增加工厂类,但不需要修改工厂类的代码,这样就解决了简单工厂模式的缺点。

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

抽象工厂模式

工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机等。这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂:

抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。抽象工厂模式的主要角色 :

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以这个案例可以使用抽象工厂模式实现

image-20230127032651220

抽象工厂

1
2
3
4
5
public interface DessertFactory {
Coffee createCoffee();

Dessert createDessert();
}

具体工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//美式甜点工厂
public class AmericanoDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new AmericanoCoffee();
}

@Override
public Dessert createDessert() {
return new MatchaMousse();
}
}

//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {

public Coffee createCoffee() {
return new LatteCoffee();
}

public Dessert createDessert() {
return new Tiramisu();
}
}

抽象产品

1
2
3
4
5
6
7
8
9
//咖啡
public abstract class Coffee {
public abstract void getName();
}

//甜品
public abstract class Dessert {
public abstract void getName();
}

具体产品

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
//美式咖啡
public class AmericanoCoffee extends Coffee {
@Override
public void getName() {
System.out.println( "美式咖啡" );
}
}
//拿铁
public class LatteCoffee extends Coffee {
@Override
public void getName() {
System.out.println( "拿铁咖啡" );
}
}
//提拉米苏
public class Tiramisu extends Dessert{
@Override
public void getName() {
System.out.println( "提拉米苏" );
}
}
//抹茶慕斯
public class MatchaMousse extends Dessert{
@Override
public void getName() {
System.out.println( "抹茶慕斯" );
}
}

甜品店

1
2
3
4
5
6
7
8
9
10
11
public class DessertStore {

public static DessertFactory createDessert( String type ) {
if ( "american".equals( type ) ) {
return new AmericanoDessertFactory();
} else if ( "italy".equals( type ) ) {
return new ItalyDessertFactory();
}
throw new RuntimeException( "没有该口味甜品" );
}
}

image-20230128034739346

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类

优点: 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象

缺点: 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改

使用场景 :

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构

如:输入法换皮肤,一整套一起换。生成不同操作系统的程序

简单工厂+配置文件解除耦合

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可

  1. 定义配置文件

    1
    2
    americano=com.hg.factory.config_factory.AmericanoCoffee
    latte=com.hg.factory.config_factory.LatteCoffee
  2. 修改工厂类

    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
    public class CoffeeFactory {
    /**
    * 创建容器对像存储咖啡对象
    */
    private static Map<String,Coffee> map = new HashMap<>();

    //加载配置文件,只加载一次
    static {
    Properties p = new Properties();
    try {
    InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream( "bean.properties" );
    //加载配置文件
    p.load( is );
    Set<Object> keySet = p.keySet();
    for ( Object key : keySet ) {
    String className = p.getProperty( ( String ) key );
    //使用反射创建对象
    Class clazz = Class.forName( className );
    Coffee coffee = ( Coffee ) clazz.newInstance();
    map.put( ( String ) key,coffee );
    }
    } catch ( Exception e ) {
    e.printStackTrace();
    }
    }

    public Coffee createCoffee( String type ){
    return map.get( type );
    }

    }

    image-20230128035610218

原型模式

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象

原型模式包含如下角色

  • 抽象原型类: 规定了具体原型对象必须实现的的 clone() 方法。
  • **具体原型类: ** 实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • **访问类: ** 使用具体原型类中的 clone() 方法来复制新的对象

image-20230128035935006

原型模式的克隆分为浅克隆和深克隆

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址

浅克隆

Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Students implements Cloneable {

public Students() {
System.out.println( "创建 Students 实列对象" );
}

@Override
protected Students clone() throws CloneNotSupportedException {
System.out.println( "浅克隆 Students 类" );
return ( Students ) super.clone();
}

}

image-20230128040340076

使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象
  • 性能和安全要求比较高

浅克隆属性引用问题

修改 Students

1
2
3
4
5
6
@Data
public class Students implements Serializable {
private String name;
private Integer age;
private Integer sex;
}

Citation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Citation implements Cloneable, Serializable {
private Students stu;

public Students getStu() {
return stu;
}

public void setStu(Students stu) {
this.stu = stu;
}

public Citation() {
System.out.println( "创建 Citation 实列对象" );
}

@Override
protected Citation clone() throws CloneNotSupportedException {
System.out.println( "克隆 Citation 实列对象" );
return ( Citation ) super.clone();
}
}

image-20230128041443097

深克隆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static void deepClone() throws IOException, ClassNotFoundException {
Citation citation1 = new Citation();
citation1.setStu( new Students( "张三",18,1 ) );
//获取对象序列化流
ObjectOutputStream oos =
new ObjectOutputStream( new FileOutputStream( "E:\\code\\designPattern\\src\\main\\resources\\clone" ) );

oos.writeObject( citation1 );
oos.close();

ObjectInputStream ois =
new ObjectInputStream( new FileInputStream( "E:\\code\\designPattern\\src\\main\\resources\\clone" ) );

Citation citation2 = ( Citation ) ois.readObject();
ois.close();

System.out.println( "citation1和citation2是否为同一个对象? " + (citation1 == citation2) );

//设置 citation2 学生信息
citation2.getStu().setName( "李四" );
//打印 citation1,2 学生信息
System.err.println( citation1.getStu() );
System.err.println( citation2.getStu() );
}

image-20230128041817747

建造者模式

将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示

image-20230129004232831

  • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

建造者(Builder)模式包含如下角色

  • 抽象建造者类(Builder): 这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者类(ConcreteBuilder): 实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product): 要创建的复杂对象。
  • 指挥者类(Director): 调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

image-20230129004602142

创建共享单车

  • 产品类

    1
    2
    3
    4
    public class Bike {
    private String frame;
    private String seat;
    }
  • 抽象建造者类

    1
    2
    3
    4
    5
    6
    7
    public abstract class Builder {
    protected Bike bike = new Bike();

    public abstract void builderFrame();
    public abstract void builderSeat();
    public abstract Bike createBike();
    }
  • 具体建造者类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //摩拜单车
    public class MobikeBuilder extends Builder{

    @Override
    public void builderFrame() {
    bike.setFrame( "摩拜单车车架" );
    }

    @Override
    public void builderSeat() {
    bike.setSeat( "摩拜单车座椅" );
    }

    @Override
    public Bike createBike() {
    return bike;
    }
    }

    //ofo单车
    public class OfoBuilder extends Builder{
    ...
    }
  • 指挥者类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Director {
    private Builder builder;

    public Director( Builder builder ) {
    this.builder = builder;
    }

    /**
    * 组装自行车,建造bike对象
    */
    public Bike conStruct(){
    builder.builderFrame();
    builder.builderSeat();
    return builder.createBike();
    }
    }

    image-20230129005330815

指挥者类  Director  在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类

优点:

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点:

造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

使用场景

建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

模式扩展

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

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
@ToString
public class Computer {
private String cpu;
private String screen;
private String memory;
private String mainBoard;

public Computer( Builder builder ) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memory = builder.memory;
this.mainBoard = builder.mainBoard;
}

public final static class Builder {
private String cpu;
private String screen;
private String memory;
private String mainBoard;

public Builder() {}

public Builder cpu( String cpu ){
this.cpu = cpu;
return this;
}
public Builder screen( String screen ){
this.screen = screen;
return this;
}
public Builder memory( String memory ){
this.memory = memory;
return this;
}
public Builder mainBoard( String mainBoard ){
this.mainBoard = mainBoard;
return this;
}

public Computer build(){
return new Computer( this );
}
}
}

image-20230129015741351

创建者模式对比

  • 工厂方法模式 vs 建造者模式

    工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

    我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

  • 抽象工厂模式 vs 建造者模式

    抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

    建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

    如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

结构型模式

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

代理模式

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。Java 中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在 Java 运行时动态生成。动态代理又有 JDK 代理和 CGLib 代理两种。

代理(Proxy)模式 分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

静态代理

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。

image-20230129023923766

售票接口(抽象主题类)

1
2
3
public interface SellTickets {
void sell();
}

代理对象(真实主题类)

1
2
3
4
5
6
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println( "火车站买票" );
}
}

代理类

1
2
3
4
5
6
7
8
9
10
11
public class ProxyPoint implements SellTickets{
private final TrainStation trainStation = new TrainStation() ;

@Override
public void sell() {
//代理类 实现功能增强
System.out.println( "代售点出售火车票收取额外费用" );
//代理对象
trainStation.sell();
}
}

image-20230129024641104

动态代理

JDK

Java中提供了一个动态代理类 Proxy,Proxy 提供了一个创建代理对象的静态方法 newProxyInstance() 来获取代理对象

1
2
3
4
5
6
7
8
9
10
11
12
//卖票接口
public interface SellTickets {
void sell();
}

//代理类 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

public void sell() {
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
25
26
27
28
29
30
31
32
public class ProxyFactory {

/**
* 声明目标对象
*/
private TrainStation trainStation = new TrainStation();

public SellTickets getProxyObject(){
/*
获取代理对象
ClassLoader loader: 类加载器,用于加载代理类,可以通过目标对象获取类加载器
Class<?>[] Interfaces: 代理类实现的接口字节码对象
InvocationHandler : 代理对象处理程序
*/
return ( SellTickets )Proxy.newProxyInstance(
trainStation.getClass().getClassLoader(),
trainStation.getClass().getInterfaces(),
/*
因为jdk动态代理是基于接口的,所以代理类必须和原始类实现相同的接口,
类加载器用于JVM动态创建代理对象,handler就是需要书写的额外功能里面有3个参数分别代表:代理对象,原始方法,原始方法的参数
*/
new InvocationHandler() {
@Override
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {
System.out.println( "jdk动态代理火车站售票" );
return method.invoke( trainStation, args );
}
}

);
}
}

image-20230131011040086

通过 Arthas 查看代理类的结构

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

public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;

public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}

static {
m3 = Class.forName("com.hg.proxy.jdk.SellTickets").getMethod("sell", new Class[0]);
}

public final void sell() {
this.h.invoke(this, m3, null);
}
}

上面的类中,看到以下几个信息

  • 代理类 $Proxy0 实现了SellTickets。这也就印证了之前说的真实类和代理类实现同样的接口
  • 代理类 $Proxy0 将我们提供了的匿名内部类对象传递给了父类

执行流程

  1. 在测试类中通过代理对象调用 sell() 方法
  2. 根据多态的特性,执行的是代理类 $Proxy0 中的sell() 方法
  3. 代理类 $Proxy0 中的 sell() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke 方法
  4. invoke方法通过反射执行了真实对象所属类 TrainStation 中的 sell() 方法
CGLib

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

引入依赖

1
2
3
4
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ProxyFactory implements MethodInterceptor {
private TrainStation trainStation = new TrainStation();

public TrainStation getProxyObject(){
//创建 Enhancer 对象,类似jdk代理中的 Proxy
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass( TrainStation.class );
//设置回调函数
enhancer.setCallback( this );
//创建代理对象
return ( TrainStation ) enhancer.create();
}

@Override
public Object intercept( Object o, Method method, Object[] objects, MethodProxy methodProxy ) throws Throwable {
method.invoke( trainStation,objects );
System.out.println( "cglib动态代理" );
return null;
}
}

三种代理的对比

  • JDK 代理和 CGLIB 代理

    • 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
    • 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
  • 动态代理静态代理

    • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
    • 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点:

  • 增加了系统的复杂度;

使用场景

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。


适配器模式

如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

适配器模式(Adapter)包含以下主要角色

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器模式

现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来

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
//SD卡接口
public interface SDCard {
String readSD();
void writeSD( String msg );
}
//TF卡接口
public interface TFCard {
String readTF();
void writeTF( String msg );
}

//SD实现类
public class SDCardImpl implements SDCard{
@Override
public String readSD() {
return "sd card read a msg :hello world SD";
}

@Override
public void writeSD( String msg ) {
System.out.println( "sd card: " + msg );
}
}
//TF实现类
public class TFCardImpl implements TFCard{
@Override
public String readTF() {
return "TF card read a msg :hello world TF";
}

@Override
public void writeTF( String msg ) {
System.out.println( "TF card: " + msg );
}
}
//计算机类
public class Computer {
public String readSD( SDCard card ){
if ( card == null ) {
throw new NullPointerException( "SDCard cannot be null" );
}
return card.readSD();
}
}

类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用

对象适配器模式

image-20230131030823980
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TFAdapterSD implements SDCard {

private TFCard tfCard;

public TFAdapterSD( TFCard tfCard ) {
this.tfCard = tfCard;
}

@Override
public String readSD() {
System.out.println( "TFAdapterSD read a msg :hello world" );
return tfCard.readTF();
}
}

还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可

应用场景

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同

装饰者模式

桥接模式

外观模式

组合模式

享元模式

快速记忆23种设计模式