《小喜子成长之路》系列--JAVA设计模式

前言

老大:**,这个任务你先看下,先想一下怎么做,然后提交一份文档给我,记住要用设计模式,别写一些没用的东西,你知道都该用哪些设计模式吧!
我:

任务来了,并且要用一下设计模式,那么先来了解一下什么是设计模式、设计模式都分为哪些、每种设计模式都有哪些、都有哪些优缺点吧!

1、什么是设计模式?

简而言之,设计模式(Design pattern)就是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的,该踩的坑别人已经替你踩了,然后总结出一些比较好的方案,你学着用就好!

2、设计模式的类型

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)

3、设计模式的六大原则

  • 开闭原则(Open Close Principle):对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

  • 里氏代换原则(Liskov Substitution Principle):里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

  • 依赖倒转原则(Dependence Inversion Principle):这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

  • 接口隔离原则(Interface Segregation Principle):这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

  • 迪米特法则,又称最少知道原则(Demeter Principle):最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

  • 合成复用原则(Composite Reuse Principle):尽量使用合成/聚合的方式,而不是使用继承。

4、各种设计模式的介绍及实现

因设计模式实在太多种了,这里只介绍工厂模式、抽象工厂模式、单例模式和策略模式,本来组合模式也要写一下的,不过发现了一点漏洞,我再看一下,晚点补充上来,如想学习其他的,请点击链接 设计模式|菜鸟教程 去查看!

1、工厂模式

使用优点:
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3、屏蔽产品的具体实现,调用者只关心产品的接口。

使用缺点:
1、每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

使用场景:
1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

注意事项:
1、作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

实现DEMO:

(1)新建接口

public interface Color {

    void according();

}
复制代码

(2) 新建接口的实现类

public class Blue implements Color {

    @Override
    public void according() {
        System.out.println("我是蓝色");
    }
}
复制代码
public class Red implements Color {

    @Override
    public void according() {
        System.out.println("我是红色");
    }
}
复制代码

(3) 创建一个工厂,生成基于给定信息的实体类的对象

public class ColorFactory {

    public Color getColor(String type){
        if (StringUtils.isEmpty(type)){
            return null;
        }
        if (type.equals("RED")){
            return new Red();
        }
        if (type.equals("BLUE")){
            return new Blue();
        }
        return null;
    }
}
复制代码

(4) 使用该工厂,通过传递类型信息来获取实体类的对象

public class ColorFactoryDemo {

    public static void main(String[] args) {

        ColorFactory colorFactory = new ColorFactory();

        // 获取蓝色类并调用方法
        Color blue = colorFactory.getColor("BLUE");
        blue.according();

        // 获取红色类调用方法
        Color red = colorFactory.getColor("RED");
        red.according();
    }
}
复制代码

(5) 查看结果

2、抽象工厂模式

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

使用缺点:
1、产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

使用场景:
1、QQ 换皮肤,一整套一起换。
2、生成不同操作系统的程序。

注意事项:
1、产品族难扩展,产品等级易扩展。

实现DEMO:

(1) 新建形状接口

public interface Shape {

    void draw();

}
复制代码

(2) 新建形状接口实现类

public class Rectangle implements Shape {

    @Override
    public void draw() {
        System.out.println("Rectangle");
    }
}
复制代码
public class Square implements Shape {

    @Override
    public void draw() {
        System.out.println("Square");
    }
}
复制代码

(3) 新建颜色接口

public interface Color {

    void fill();
}
复制代码

(4) 新建颜色接口实现类

public class Red implements Color {

    @Override
    public void fill() {
        System.out.println("Red");
    }
}
复制代码
public class Blue implements Color {

    @Override
    public void fill() {
        System.out.println("Blue");
    }
}
复制代码

(5) 为 Color 和 Shape 对象创建抽象类来获取工厂

public interface AbstractFactory {

    /**
     * 获取颜色
     *
     * @param color
     * @return
     */
    public abstract Color getColor(String color);

    /**
     * 获取形状
     *
     * @param shape
     * @return
     */
    public abstract Shape getShape(String shape);
}
复制代码

(6) 为 Color 和 Shape 对象创建抽象类来获取工厂

public abstract class AbstractFactory {

    /**
     * 获取颜色
     *
     * @param color
     * @return
     */
    public abstract Color getColor(String color);

    /**
     * 获取形状
     *
     * @param shape
     * @return
     */
    public abstract Shape getShape(String shape);
}
复制代码

(7) 创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象

public class ShapeFactory extends AbstractFactory {

    @Override
    public Color getColor(String color) {
        return null;
    }

    @Override
    public Shape getShape(String shape) {
        if (StringUtils.isEmpty(shape)){
            return null;
        }

        if (shape.equalsIgnoreCase("SQUARE")){
            return new Square();
        }

        if (shape.equalsIgnoreCase("RECTANGLE")){
            return new Rectangle();
        }
        return null;
    }
}
复制代码
public class ColorFactory extends AbstractFactory {

    @Override
    public Color getColor(String color) {
        if (StringUtils.isEmpty(color)){
            return null;
        }

        if (color.equalsIgnoreCase("RED")){
            return new Red();
        }

        if (color.equalsIgnoreCase("BLUE")){
            return new Blue();
        }
        return null;
    }

    @Override
    public Shape getShape(String shape) {
        return null;
    }
}
复制代码

(8) 创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂

public class FactoryProducer {
    public static AbstractFactory getFactory(String type){
        if (StringUtils.isEmpty(type)){
            return null;
        }

        if (type.equalsIgnoreCase("SHAPE")){
            return new ShapeFactory();
        }else if (type.equalsIgnoreCase("COLOR")){
            return new ColorFactory();
        }

        return null;
    }
}
复制代码

(9) 使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象

public class AbstractFactoryPatternDemo {

    public static void main(String[] args){

        // 获取形状工厂
        AbstractFactory shape = FactoryProducer.getFactory("SHAPE");

        // 获取颜色工厂
        AbstractFactory color = FactoryProducer.getFactory("COLOR");

        // 获取形状为 Rectangle 的对象
        Shape rectangle = shape.getShape("RECTANGLE");
        rectangle.draw();

        // 获取形状为 Square 的对象
        Shape square = shape.getShape("SQUARE");
        square.draw();

        //获取颜色为 Red 的对象
        Color red = color.getColor("RED");
        red.fill();

        // 获取颜色为 Blue 的对象
        Color blue = color.getColor("BLUE");
        blue.fill();

    }
}
复制代码

(10) 运行,查看结果

3、单例模式

使用优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。

使用缺点:
1、没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:
1、getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

实现DEMO:

(1) 新建 Singleton 类,提供获取对象的方法

public class SingleObject {

    // 创建 SingleObject 的一个对象
    private static SingleObject singleObject = new SingleObject();

    //让构造函数为 private,这样该类就不会被实例化
    private SingleObject(){}

    //获取唯一可用的对象
    public static SingleObject getInstance(){
        return singleObject;
    }

    public void showMessage(){
        System.out.println("Hello SingleObject!");
    }
}
复制代码

(2) 从 singleton 类获取唯一的对象并调用方法

public class SingleObjectDemo {

    public static void main(String[] args) {

        //不合法的构造函数
        //编译时错误:构造函数 SingleObject() 是不可见的
        //SingleObject object = new SingleObject();

        //获取唯一可用的对象
        SingleObject object = SingleObject.getInstance();

        //显示消息
        object.showMessage();
    }
}
复制代码

(3) 查看结果

单例模式的几种实现方式

(1) 懒汉式,线程不安全

public class LazyThreadNotSafety {

    private static LazyThreadNotSafety lazyThreadSafety;

    private LazyThreadNotSafety(){

    }

    public static LazyThreadNotSafety getInstance(){
        if (lazyThreadSafety == null){
            lazyThreadSafety = new LazyThreadNotSafety();
        }
        return lazyThreadSafety;
    }
}
复制代码

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

(2) 懒汉式,线程安全

public class LazyThreadSafety {
    
    private static LazyThreadSafety lazyThreadSafety;
    
    private LazyThreadSafety(){
        
    }
    
    public static synchronized LazyThreadSafety getInstance(){
        if (lazyThreadSafety == null){
            lazyThreadSafety = new LazyThreadSafety();
        }
        return lazyThreadSafety;
    }
}
复制代码

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。 getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

(3) 饿汉式

public class HungryType {
    
    private static HungryType hungryType = new HungryType();
    
    private HungryType(){
        
    }
    
    public static HungryType getInstance(){
        return hungryType;
    }
}
复制代码

描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

(4) 双检锁/双重校验锁

public class DoubleLock {
    
    private volatile static DoubleLock doubleLock;
    
    private DoubleLock(){
        
    }
    
    public static DoubleLock getInstance(){
        if (doubleLock == null){
            synchronized (DoubleLock.class){
                if (doubleLock == null){
                    doubleLock = new DoubleLock();
                }
            }
        }
        return doubleLock;
    }
}
复制代码

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

(5) 登记式/静态内部类

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}
复制代码

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

(6) 枚举

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}
复制代码

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

4、策略模式

使用优点:
1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。

使用缺点:
1、策略类会增多。
2、所有策略类都需要对外暴露。

使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项:
如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

实现DEMO:

(1) 新建策略类应该实现的接口

public interface Strategy {

    public int doOperation(int num1, int num2);

}
复制代码

(2) 新建三个策略类

@Service
public class StrategyAdd implements Strategy {

    @Override
    public int doOperation(int num1, int num2) {
        return num1 + num2;
    }
}
复制代码
@Service
public class StrategyReduction implements Strategy {

    @Override
    public int doOperation(int num1, int num2) {
        return num1 - num2;
    }
}

复制代码
@Service
public class StrategyTake implements Strategy {

    @Override
    public int doOperation(int num1, int num2) {
        return num1 * num2;
    }
}
复制代码

(3) 创建 Context 类

public class Context {
    private Strategy strategy;

    public Context(Strategy strategy){
        this.strategy = strategy;
    }

    public int executeStrategy(int num1, int num2){
        return strategy.doOperation(num1, num2);
    }
}
复制代码

(4) 新建策略枚举类

public enum StrategyEnum {

    ADD("strategyAdd"),

    SUB("strategyReduction"),

    TAKE("strategyTake");

    /** 描述 */
    private String clazz;

    private StrategyEnum(String clazz) {
        this.clazz = clazz;
    }

    public String getClazz() {
        return clazz;
    }

    public void setClazz(String clazz) {
        this.clazz = clazz;
    }

    public static Map<String, String> toMap() {
        Map<String, String> enumMap = new HashMap<String, String>();

        StrategyEnum[] ary = StrategyEnum.values();
        for (int i = 0, size = ary.length; i < size; i++) {
            enumMap.put(ary[i].name(), ary[i].getClazz());
        }

        return enumMap;
    }
}
复制代码

(5) 创建 SpringContextUtils 类

@Component
public class SpringContextUtils implements ApplicationContextAware {

    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }

}
复制代码

(6) 使用 StrategyDemo 实现 CommandLineRunner接口来实现启动之后调用run方法来查看当它改变策略 Strategy 时的行为变化。

@Component
public class StrategyDemo implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        // 获取枚举类中的相应处理类型
        String clazzName = getClassName("ADD");
        // 通过反射获取类
        Object strategyAdd = SpringContextUtils.getBean("strategyAdd");
        Strategy bean = SpringContextUtils.getBean(clazzName, Strategy.class);
        Context context = new Context(bean);
        System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

        // 获取枚举类中的相应处理类型
        String clazzName1 = getClassName("SUB");
        // 通过反射获取类
        Strategy bean1 = SpringContextUtils.getBean(clazzName1, Strategy.class);
        context = new Context(bean1);
        System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

        // 获取枚举类中的相应处理类型
        String clazzName2 = getClassName("TAKE");
        // 通过反射获取类
        Strategy bean2 = SpringContextUtils.getBean(clazzName2, Strategy.class);
        context = new Context(bean2);
        System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
    }

    /**
     * 获取对应的策略类
     *
     * @param type
     * @return
     */
    private static String getClassName(String type) {
        String clazzName = "";
        Map<String, String> maps = StrategyEnum.toMap();
        for (Map.Entry<String, String> entry : maps.entrySet()) {
            if (entry.getKey().equalsIgnoreCase(type)) {
                clazzName = entry.getValue();
                break;
            }
        }
        return clazzName;
    }
}

复制代码

(7) 查看结果

策略模式有很重要的几点:

1、枚举类中各个策略类必须首字母要小些,否则SpringContextUtils.getBean是获取不到的。
2、策略类必须加@Service,必须注入到spring容器中,不然无法通过反射获取实例。
3、SpringContextUtils类是通过反射获取相应实例的,而SpringContextUtils是实现了ApplicationContextAware接口,启动springboot项目之后,setApplicationContext方法来注入ApplicationContext的实例,所以这里不能用main方法直接启动,那样的话ApplicationContext的实例是无法注入的。
4、StrategyDemo类必须加@Component注解,将此类实例化到spring容器中,要不然项目启动是无法调用run方法的!


还有很多设计模式,是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的,大家可以去菜鸟教程都看一下,项目中遇到的话可以应用起来,这篇文章以后会补充的,今天就先写到这里,谢谢!

猜你喜欢

转载自juejin.im/post/5e81fd746fb9a03c90377e15