0010 设计模式之创建型模式

Posted on Sat, May 28, 2022 JAVA DP 设计模式

创建型模式是对类的实例化的抽象,对类的创建和使用分离。主要包括以下6种。

1、工厂方法模式

1.1 定义

🥝

定义:工厂父类负责定义创建产品对象的公共接口,由工厂子类负责生产具体产品对象,即将产品的创建延迟到工厂子类中完成,工厂子类来决定创建哪一种产品。

工厂方法模式包含如下角色:

1.2 实践

实践用例:实现一个日志记录器,支持多种日志记录方式,如文件记录、数据库记录等,且用户可以根据要求动态选择日志记录方式, 现使用工厂方法模式设计该系统。
package factorymethod;

abstract public class LogFactory {
    abstract Log createLog();

}

其中,使用抽象的类,以及抽象的方法。现在让我们还需要一个抽象的日志类。

package factorymethod;

abstract public class Log {
    abstract public void writeLog();
}

现在,抽象层的内容已经实现完毕,可以接活了。此时来了一个需求:做一个文件日志记录器,这个工具用来记录文件操作的日志。

有了抽象层的基础,我们要做的无非就是实现对应的两个的抽象类及其方法:

1、实现日志工厂子类

package factorymethod;

public class FileLogFactory extends LogFactory{
    @Override
    public Log createLog() {
        System.out.println("create File Log Factory");
        return new FileLog();
    }
}

2、实现文件日志记录的具体方法

package factorymethod;

public class FileLog extends Log{
    @Override
    public void writeLog() {
        System.out.println("File Log is writing...");
    }
}

我们来测试一下这个文件日志记录器:

package factorymethod;

import org.junit.Test;

public class TestEntrance {

    @Test
    public void testFileLog() {
        LogFactory logFactory = new FileLogFactory();
        Log log = logFactory.createLog();

        log.writeLog();
    }

}

运行之后打印:

create File Log Factory File Log is writing...

软件工程中,唯一不变的就是变化。现在新的需求来了,我们需要一个数据库日志记录器:专门记录日志操作的的日志。这简直就是个A-ha时刻,因为我们可以如法炮制。

1、实现一个数据库日志工厂子类

package factorymethod;

public class DBLogFactory extends LogFactory{
    @Override
    public Log createLog() {
        System.out.println("create DB Log Factory");
        return new DBLog();
    }
}

2、实现数据库日志记录的具体方法

package factorymethod;

public class DBLog extends Log {
    @Override
    public void writeLog() {
        System.out.println("DB Log is writing...");
    }
}

测试一下数据库日志记录器:

package factorymethod;

import org.junit.Test;

public class TestEntrance {
    @Test
    public void testDBLog() {
        LogFactory logFactory = new DBLogFactory();
        Log log = logFactory.createLog();

        log.writeLog();
    }
}

运行之后打印:

create DB Log Factory DB Log is writing...

1.3 工厂方法模式的优点

1.4 工厂方法模式的缺点

2、抽象工厂模式

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。

什么时候适合使用抽象工厂模式呢?

2.1 定义

🥝

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。

抽象工厂模式包含如下角色:

2.2 实践

实践用例:用抽象类分别实现:人,餐具、食物。创建一个工厂子类,可以描述世界上的人用什么餐具来吃什么食物。比如:美国人用餐叉吃牛排,中国人用筷子吃面条。
package abstractfactory;

abstract public class AbstractFactory {
    // 人
    public abstract People createPeople();
    // 餐具
    public abstract Tableware createTableware();
    // 食物
    public abstract Food createFood();
}

其中,人的抽象类:

package abstractfactory;
/**
*@Desc: 人抽象类
*/
abstract public class People {
    abstract public void who();
}

餐具抽象类:

package abstractfactory;
/**
*@Desc: 餐具抽象类
*/
abstract public class Tableware {
    abstract public void use();
}

食物抽象类:

package abstractfactory;
/**
*@Desc: 食物抽象类
*/
abstract public class Food {
    abstract public void name();
}
package abstractfactory.eatsteak;

import abstractfactory.AbstractFactory;
import abstractfactory.Food;
import abstractfactory.People;
import abstractfactory.Tableware;

public class EatSteakFactory extends AbstractFactory {
    @Override
    public People createPeople() {
        return new Americans();
    }

    @Override
    public Tableware createTableware() {
        return new Fork();
    }
    @Override
    public Food createFood() {
        return new Steak();
    }

}

其中,实现对应的抽象类:

package abstractfactory.eatsteak;

import abstractfactory.People;
/**
 *@Desc: 人-美国人
 */
public class Americans extends People {
    @Override
    public void who() {
        System.out.println("Americans");
    }
}
package abstractfactory.eatsteak;

import abstractfactory.Tableware;

/**
*@Desc: 餐具-餐叉
*/
public class Fork extends Tableware {
    @Override
    public void use() {
        System.out.println("use fork to eat");
    }
}
package abstractfactory.eatsteak;

import abstractfactory.Food;

/**
*@Desc: 食物-牛排
*/
public class Steak extends Food {
    @Override
    public void name() {
        System.out.println("steak.");
    }
}

我们来测试美国人用餐叉吃牛排,执行测试:

package abstractfactory;

import abstractfactory.eatsteak.EatSteakFactory;
import org.junit.Test;

public class TestEntrance {
    @Test
    public void testEatSteak() {
        AbstractFactory factory = new EatSteakFactory();
        // 人
        People people = factory.createPeople();
        // 餐具
        Tableware tableware = factory.createTableware();
        // 食物
        Food food = factory.createFood();

        people.who();
        tableware.use();
        food.name();

    }
}

执行结果

Americans use fork to eat steak.

现在让我们来实现一下中国人用筷子吃面条。首选分别实现对应的抽象类:

package abstractfactory.eatnoodles;

import abstractfactory.AbstractFactory;
import abstractfactory.Food;
import abstractfactory.People;
import abstractfactory.Tableware;

public class EatNoodlesFactory extends AbstractFactory {
    @Override
    public People createPeople() {
        return new Chinese();
    }

    @Override
    public Tableware createTableware() {
        return new Chopsticks();
    }

    @Override
    public Food createFood() {
        return new Noodles();
    }
}

其中

package abstractfactory.eatnoodles;

import abstractfactory.People;

public class Chinese extends People {
    @Override
    public void who() {
        System.out.println("Chinese");
    }
}
package abstractfactory.eatnoodles;

import abstractfactory.Tableware;

public class Chopsticks extends Tableware {
    @Override
    public void use() {
        System.out.println("use chopsticks to eat");
    }
}
package abstractfactory.eatnoodles;

import abstractfactory.Food;

public class Noodles extends Food {
    @Override
    public void name() {
        System.out.println("noodles");
    }
}

我们来测试美国人用餐叉吃牛排,执行测试:

package abstractfactory;

import abstractfactory.eatnoodles.EatNoodlesFactory;
import abstractfactory.eatsteak.EatSteakFactory;
import org.junit.Test;

public class TestEntrance {
    @Test
    public void testEatNoodles() {
        AbstractFactory factory = new EatNoodlesFactory();
        // 人
        People people = factory.createPeople();
        // 餐具
        Tableware tableware = factory.createTableware();
        // 食物
        Food food = factory.createFood();

        people.who();
        tableware.use();
        food.name();
    }
}

执行结果

Chinese use chopsticks to eat noodles

2.3 抽象工厂模式的优点

2.4 缺点

3、单例模式

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

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

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

3.1 定义

🥝

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:

目前单列类的实现,比较推荐的做法有两种:

package singleton;

public class Singleton {

    private volatile static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
package singleton;

import java.lang.reflect.Constructor;

public class SingletonByReflection {
    private static SingletonByReflection instance;

    static {
        try {
            Class<?> cl = Class.forName(SingletonByReflection.class.getName());
            //获得无参构造
            Constructor<?> con = cl.getDeclaredConstructor();
            //设置无参构造是可访问的
            con.setAccessible(true);
            //产生一个实例对象
            instance = (SingletonByReflection) con.newInstance();
        } catch (Exception e) {

        }
    }

    public static SingletonByReflection getInstance() {
        return instance;
    }
}
package singleton;

public enum  EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

其他实现方式这里不做展开,在单例模式的实现过程中,需要注意如下三点:

3.2 优点

3.3 缺点

4、简单工厂模式

4.1 定义

🥝

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

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

4.2 实践

实现案例:模拟点餐场景,后厨根据用户点的食物名称,做对应的食物。
package simplefactory;

abstract public class SimpleFactory {
    abstract public Food createFood(String foodName);
}
package simplefactory;

abstract public class Food {
    abstract public void name();
}

现在厨房今日的菜单上有三个菜可选,分别为:面条,牛肉、米饭。我们分别创建对应的类:

package simplefactory;

public class Noodles extends Food {
    @Override
    public void name() {
        System.out.println("this is a bowl of noodles.");
    }
}
package simplefactory;

public class Beef extends Food {
    @Override
    public void name() {
        System.out.println("this is a piece of beef.");
    }
}
package simplefactory;

public class Rice extends Food {
    @Override
    public void name() {
        System.out.println("this is a bowl of rice.");
    }
}

点餐工具就是一个简单工厂类的实现:

package simplefactory;

public class FoodSimpleFactory extends SimpleFactory {
    @Override
    public Food createFood(String foodName) {
        if (FoodEnum.NOODLES.name().equals(foodName)) {
            return new Noodles();
        } else if (FoodEnum.RICE.name().equals(foodName)) {
            return new Rice();
        } else if (FoodEnum.BEEF.name().equals(foodName)) {
            return new Beef();
        } else {
            System.out.println("不支持的点餐类型");
            return null;
        }

    }
}
package simplefactory;

public enum FoodEnum {
    RICE,
    NOODLES,
    BEEF
}

我们来测试一下,一个点了面条的场景:

package simplefactory;

import org.junit.Test;

public class TestEnctrance {
    @Test
    public void testNoodles() {
        SimpleFactory simpleFactory = new FoodSimpleFactory();

        Food food = simpleFactory.createFood(FoodEnum.NOODLES.name());

        food.name();
    }
}

执行结果:

this is a bowl of noodles.

点一碗米饭:

package simplefactory;

import org.junit.Test;

public class TestEnctrance {
    @Test
    public void testRice() {
        SimpleFactory simpleFactory = new FoodSimpleFactory();

        Food food = simpleFactory.createFood(FoodEnum.RICE.name());

        food.name();
    }
}

执行结果:

this is a bowl of rice.

点一份牛排

package simplefactory;

import org.junit.Test;

public class TestEnctrance {
    @Test
    public void testBeef() {
        SimpleFactory simpleFactory = new FoodSimpleFactory();

        Food food = simpleFactory.createFood(FoodEnum.BEEF.name());

        food.name();
    }
}

执行结果:

this is a piece of beef.

4.3 简单工厂模式的优点

4.4 简单工厂模式的缺点

5、原型模式

参考:

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。 意图: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 主要解决: 在运行期建立和删除原型。 何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。 如何解决: 利用已有的一个原型对象,快速地生成和原型对象一样的实例。 关键代码: 1、实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。 应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。 优点: 1、性能提高。 2、逃避构造函数的约束。 缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。 使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

6、建造者模式

这部分在之前的个人博客中有详细介绍:

0006 优雅与高效的java编程手记(1-20)

7、文中代码

https://github.com/gendlee/desgin_patterns_practice

参考文献

图说设计模式 - Graphic Design Patterns
原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。 意图: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 主要解决: 在运行期建立和删除原型。 何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。 如何解决: 利用已有的一个原型对象,快速地生成和原型对象一样的实例。 关键代码: 1、实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。 应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。 优点: 1、性能提高。 2、逃避构造函数的约束。 缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。 使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。