设计模式Demo

170 阅读12分钟

前言

本文直接通过代码demo,介绍单例模式、建造者模式、工厂模式、策略模式、模板方法模式、责任链模式、代理模式、适配器模式、观察者模式。

1、单例模式

1.1、静态常量

public static final AnnotationBeanNameGenerator INSTANCE = new AnnotationBeanNameGenerator();

1.2、双重检查机制

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;
    }

}

1.3、枚举

public enum IpRegionCilent {
    INSTANCE;
    private Searcher instance;

    IpRegionCilent() {
        try {
            String dbPath = ResourceUtils.getURL("classpath:").getPath() + "ip2region.xdb";
            // 1、从 dbPath 加载整个 xdb 到内存。
            byte[] cBuff = Searcher.loadContentFromFile(dbPath);
            // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
            instance = Searcher.newWithBuffer(cBuff);
        } catch (Exception e) {
            log.error("IpRegionCilent new instance error", e);
        }
    }

    public Searcher getInstance() {
        return instance;
    }
}

2、建造者模式

PersonDTO personDTO = PersonDTO.builder()  
        .name("三友的java日记")  
        .age(18)  
        .sex(1)  
        .phone("188****9527")  
        .build();

3、工厂模式

工厂模式在开源项目中也使用的非常多,具体的实现大概可以细分为三种:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

3.1、简单工厂模式

简单工厂模式,就跟名字一样简单。比如说,现在有个动物接口Animal,具体的实现有猫Cat、狗Dog等等,而每个具体的动物对象创建过程很复杂,有各种各样地步骤,此时就可以使用简单工厂来封装对象的创建过程,调用者不需要关心对象是如何具体创建的。

public class SimpleAnimalFactory {

    public Animal createAnimal(String animalType) {
        if ("cat".equals(animalType)) {
            Cat cat = new Cat();
            //一系列复杂操作
            return cat;
        } else if ("dog".equals(animalType)) {
            Dog dog = new Dog();
            //一系列复杂操作
            return dog;
        } else {
            throw new RuntimeException("animalType=" + animalType + "无法创建对应对象");
        }
    }

}

当需要使用这些对象,调用者就可以直接通过简单工厂创建就行。

SimpleAnimalFactory animalFactory = new SimpleAnimalFactory();
Animal cat = animalFactory.createAnimal("cat");

需要注意的是,一般来说如果每个动物对象的创建只需要简单地new一下就行了,那么其实就无需使用工厂模式,工厂模式适合对象创建过程复杂的场景。

3.2、工厂方法模式

上面说的简单工厂模式看起来没啥问题,但是还是违反了七大设计原则的OCP原则,也就是开闭原则。所谓的开闭原则就是对修改关闭,对扩展开放。

什么叫对修改关闭?就是尽可能不修改的意思。就拿上面的例子来说,如果现在新增了一种动物兔子,那么createAnimal方法就得修改,增加一种类型的判断,那么就此时就出现了修改代码的行为,也就违反了对修改关闭的原则。

所以解决简单工厂模式违反开闭原则的问题,就可以使用工厂方法模式来解决。

/**
 * 工厂接口
 */
public interface AnimalFactory {
    Animal createAnimal();
}

/**
 * 小猫实现
 */
public class CatFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        Cat cat = new Cat();
        //一系列复杂操作
        return cat;
    }
}

/**
 * 小狗实现
 */
public class DogFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        Dog dog = new Dog();
        //一系列复杂操作
        return dog;
    }
}

这种方式就是工厂方法模式。他将动物工厂提取成一个接口AnimalFactory,具体每个动物都各自实现这个接口,每种动物都有各自的创建工厂,如果调用者需要创建动物,就可以通过各自的工厂来实现。

AnimalFactory animalFactory = new CatFactory();
Animal cat = animalFactory.createAnimal();

此时假设需要新增一个动物兔子,那么只需要实现AnimalFactory接口就行,对于原来的猫和狗的实现,其实代码是不需要修改的,遵守了对修改关闭的原则,同时由于是对扩展开放,实现接口就是扩展的意思,那么也就符合扩展开放的原则。

3.3、抽象工厂模式

工厂方法模式其实是创建一个产品的工厂,比如上面的例子中,AnimalFactory其实只创建动物这一个产品。而抽象工厂模式特点就是创建一系列产品,比如说,不同的动物吃的东西是不一样的,那么就可以加入食物这个产品,通过抽象工厂模式来实现。

public interface AnimalFactory {

    Animal createAnimal();

    Food createFood();
        
}

在动物工厂中,新增了创建食物的接口,小狗小猫的工厂去实现这个接口,创建狗粮和猫粮,这里就不去写了。

4、策略模式

假设现在有一个需求,需要将消息推送到不同的平台。

最简单的做法其实就是使用if else来做判断就行了。

public void notifyMessage(User user, String content, int notifyType) {
    if (notifyType == 0) {
        //调用短信通知的api发送短信
    } else if (notifyType == 1) {
        //调用app通知的api发送消息
    }
}

根据不同的平台类型进行判断,调用对应的api发送消息。

虽然这样能实现功能,但是跟上面的提到的简单工厂的问题是一样的,同样违反了开闭原则。当需要增加一种平台类型,比如邮件通知,那么就得修改notifyMessage的方法,再次进行else if的判断,然后调用发送邮件的邮件发送消息。

此时就可以使用策略模式来优化了。

首先设计一个策略接口:

public interface MessageNotifier {

    /**
     * 是否支持改类型的通知的方式
     *
     * @param notifyType 0:短信 1:app
     * @return
     */
    boolean support(int notifyType);

    /**
     * 通知
     *
     * @param user
     * @param content
     */
    void notify(User user, String content);

}

短信通知实现:

@Component
public class SMSMessageNotifier implements MessageNotifier {
    @Override
    public boolean support(int notifyType) {
        return notifyType == 0;
    }

    @Override
    public void notify(User user, String content) {
        //调用短信通知的api发送短信
    }
}

app通知实现:

public class AppMessageNotifier implements MessageNotifier {
    @Override
    public boolean support(int notifyType) {
        return notifyType == 1;
    }

    @Override
    public void notify(User user, String content) {
       //调用通知app通知的api
    }
}

最后notifyMessage的实现只需要要循环调用所有的MessageNotifier的support方法,一旦support方法返回true,说明当前MessageNotifier支持该类的消息发送,最后再调用notify发送消息就可以了。

@Resource
private List<MessageNotifier> messageNotifiers;

public void notifyMessage(User user, String content, int notifyType) {
    for (MessageNotifier messageNotifier : messageNotifiers) {
        if (messageNotifier.support(notifyType)) {
            messageNotifier.notify(user, content);
        }
    }
}

那么如果现在需要支持通过邮件通知,只需要实现MessageNotifier接口,注入到Spring容器就行,其余的代码根本不需要有任何变动。

到这其实可以更好的理解策略模式了。就拿上面举的例子来说,短信通知,app通知等其实都是发送消息一种策略,而策略模式就是需要将这些策略进行封装,抽取共性,使这些策略之间相互替换。

5、模板方法模式

模板方法模式是指,在父类中定义一个操作中的框架,而操作步骤的具体实现交由子类做。其核心思想就是,对于功能实现的顺序步骤是一定的,但是具体每一步如何实现交由子类决定。

比如说,对于旅游来说,一般有以下几个步骤:

  • 做攻略,选择目的地
  • 收拾行李
  • 乘坐交通工具去目的地
  • 玩耍、拍照
  • 乘坐交通工具去返回

但是对于去哪,收拾什么东西都,乘坐什么交通工具,都是由具体某个旅行来决定。

那么对于旅游这个过程使用模板方法模式翻译成代码如下:

public abstract class Travel {

    public void travel() {
        //做攻略
        makePlan();

        //收拾行李
        packUp();

        //去目的地
        toDestination();

        //玩耍、拍照
        play();

        //乘坐交通工具去返回
        backHome();
    }

    protected abstract void makePlan();

    protected abstract void packUp();

    protected abstract void toDestination();

    protected abstract void play();

    protected abstract void backHome();

}

去长城的实现类:

public class Changcheng extends Travel{

    @Override
    protected void makePlan() {
        System.out.println("makePlan");
    }

    @Override
    protected void packUp() {
        System.out.println("packUp");
    }

    @Override
    protected void toDestination() {
        System.out.println("toDestination");
    }

    @Override
    protected void play() {
        System.out.println("play");
    }

    @Override
    protected void backHome() {
        System.out.println("backHome");
    }

    @Override
    public void travel() {
        super.travel();
    }
}

测试:

public static void main(String[] args) {
    Changcheng test = new Changcheng();
    test.travel();
}

对于某次旅行来说,只需要重写每个步骤该做的事就行,比如说这次可以选择去杭州西湖,下次可以去长城,但是对于旅行过程来说是不变了,对于调用者来说,只需要调用暴露的travel方法就行。

6、责任链模式

在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,由该链上的某一个对象或者某几个对象决定处理此请求,每个对象在整个处理过程中值扮演一个小小的角色。

举个例子,现在有个请假的审批流程,根据请假的人的级别审批到的领导不同,比如有有组长、主管、HR、分管经理等等。

先需要定义一个处理抽象类,抽象类有个下一个处理对象的引用,提供了抽象处理方法,还有一个对下一个处理对象的调用方法。

请假对象:

@Data
@ToString
public class ApprovalContext {

    /**
     * 组长意见
     */
    private String  groupLeaderComment;
    /**
     * 主管意见
     */
    private String  directorComment;
    /**
     * 认识意见
     */
    private String  hrComment;
    /**
     * 内容
     */
    private String  context;
}

请假Handler:

public abstract class ApprovalHandler {

    /**
     * 责任链中的下一个处理对象
     */
    protected ApprovalHandler next;

    /**
     * 设置下一个处理对象
     *
     * @param approvalHandler
     */
    public void nextHandler(ApprovalHandler approvalHandler) {
        this.next = approvalHandler;
    }

    /**
     * 处理
     *
     * @param approvalContext
     */
    public abstract void approval(ApprovalContext approvalContext);

    /**
     * 调用下一个处理对象
     *
     * @param approvalContext
     */
    protected void invokeNext(ApprovalContext approvalContext) {
        if (next != null) {
            next.approval(approvalContext);
        }
    }

}

几种审批人的实现

//组长审批实现
public class GroupLeaderApprovalHandler extends ApprovalHandler {
    @Override
    public void approval(ApprovalContext approvalContext) {
        System.out.println("组长审批");
        approvalContext.setGroupLeaderComment("同意");
        //调用下一个处理对象进行处理
        invokeNext(approvalContext);
    }
}

//主管审批实现
public class DirectorApprovalHandler extends ApprovalHandler {
    @Override
    public void approval(ApprovalContext approvalContext) {
        System.out.println("主管审批");
        approvalContext.setGroupLeaderComment("同意");
        //调用下一个处理对象进行处理
        invokeNext(approvalContext);
    }
}

//hr审批实现
public class HrApprovalHandler extends ApprovalHandler {
    @Override
    public void approval(ApprovalContext approvalContext) {
        System.out.println("hr审批");
        approvalContext.setGroupLeaderComment("同意");
        //调用下一个处理对象进行处理
        invokeNext(approvalContext);
    }
}

有了这几个实现之后,接下来就需要对对象进行组装,组成一个链条,比如在Spring中就可以这么玩。

@Component
public class ApprovalHandlerChain {

    @Autowired
    private GroupLeaderApprovalHandler groupLeaderApprovalHandler;
    @Autowired
    private DirectorApprovalHandler directorApprovalHandler;
    @Autowired
    private HrApprovalHandler hrApprovalHandler;

    public ApprovalHandler getChain() {
        //组长处理完下一个处理对象是主管
        groupLeaderApprovalHandler.nextHandler(directorApprovalHandler);
        //主管处理完下一个处理对象是hr
        directorApprovalHandler.nextHandler(hrApprovalHandler);
        
        //返回组长,这样就从组长开始审批,一条链就完成了
        return groupLeaderApprovalHandler;
    }

}

测试:

public static void main(String[] args) {
     //提交请假流程
    ApprovalContext approvalContext = new ApprovalContext();
    approvalContext.setContext("面试跑路了,需要请假");

    //组装审批链
    ApprovalHandler chain = getChain();
    //审批
    chain.approval(approvalContext);
    //通知结果
    System.out.println(approvalContext.toString());
}

之后对于调用方而言,只需要获取到链条,开始处理就行。

一旦后面出现需要增加或者减少审批人,只需要调整链条中的节点就行,对于调用者来说是无感知的。

7、代理模式

代理模式也是开源项目中很常见的使用的一种设计模式,这种模式可以在不改变原有代码的情况下增加功能。 举个例子,比如现在有个PersonService接口和它的实现类PersonServiceImpl //接口 public interface PersonService {

void savePerson(PersonDTO person);

}

//实现 public class PersonServiceImpl implements PersonService{ @Override public void savePerson(PersonDTO person) { //保存人员信息 } } 这个类刚开始运行的好好的,但是突然之前不知道咋回事了,有报错,需要追寻入参,所以此时就可以这么写。 public class PersonServiceImpl implements PersonService { @Override public void savePerson(PersonDTO person) { log.info("savePerson接口入参:{}", JSON.toJSONString(person)); //保存人员信息 } } 这么写,就修改了代码,万一以后不需要打印日志了呢,岂不是又要修改代码,不符和之前说的开闭原则,那么怎么写呢?可以这么玩。 public class PersonServiceProxy implements PersonService {

private final PersonService personService = new PersonServiceImpl();

@Override
public void savePerson(PersonDTO person) {
    log.info("savePerson接口入参:{}", JSON.toJSONString(person));
    personService.savePerson(person);
}

} 可以实现一个代理类PersonServiceProxy,对PersonServiceImpl进行代理,这个代理类干的事就是打印日志,最后调用PersonServiceImpl进行人员信息的保存,这就是代理模式。 当需要打印日志就使用PersonServiceProxy,不需要打印日志就使用PersonServiceImpl,这样就行了,不需要改原有代码的实现。 讲到了代理模式,就不得不提一下Spring AOP,Spring AOP其实跟静态代理很像,最终其实也是调用目标对象的方法,只不过是动态生成的,详细文章请看

8、适配器模式

适配器模式使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作,将一个类的接口转换成客户希望的另一个接口。

举个生活中的例子,比如手机充电器接口类型有USB TypeC接口和Micro USB接口等。现在需要给一个Micro USB接口的手机充电,但是现在只有USB TypeC接口的充电器,这怎么办呢?

其实一般可以弄个一个USB TypeC转Micro USB接口的转接头,这样就可以给Micro USB接口手机充电了,代码如下

USBTypeC接口充电

public class USBTypeC {

    public void chargeTypeC() {
        System.out.println("开启充电了");
    }

}

MicroUSB接口

public interface MicroUSB {

    void charge();

}

适配实现,最后是调用USBTypeC接口来充电

public class MicroUSBAdapter implements MicroUSB {

    private final USBTypeC usbTypeC = new USBTypeC();

    @Override
    public void charge() {
        //使用usb来充电
        usbTypeC.chargeTypeC();
    }

}

方然除了上面这种写法,还有一种继承的写法。

public class MicroUSBAdapter extends USBTypeC implements MicroUSB {

    @Override
    public void charge() {
        //使用usb来充电
        this.chargeTypeC();
    }

}

这两种写法主要是继承和组合(聚合)的区别。

这样就可以通过适配器(转接头)就可以实现USBTypeC给MicroUSB接口充电。

9、观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。

这是什么意思呢,举个例子来说,假设发生了火灾,可能需要打119、救人,那么就可以基于观察者模式来实现,打119、救人的操作只需要观察火灾的发生,一旦发生,就触发相应的逻辑。

图片

观察者的核心优点就是观察者和被观察者是解耦合的。就拿上面的例子来说,火灾事件(被观察者)根本不关系有几个监听器(观察者),当以后需要有变动,只需要扩展监听器就行,对于事件的发布者和其它监听器是无需做任何改变的。

观察者模式实现起来比较复杂,这里我举一下例子来说明一下:

一、创建事件对象

@Getter
@Setter
public class DoorEvent extends EventObject{

    int state;

    public DoorEvent(Object source){
        super(source);
    }
    public DoorEvent(Object source,int state){
        super(source);
        this.state = state;
    }
}

二、事件监听器

public interface DoorListener extends EventListener{
    void doorEvent(DoorEvent doorEvent);
}
public class CloseDoorEvent implements DoorListener{
    @Override
    public void doorEvent(DoorEvent doorEvent){
        if(doorEvent.getState() == -1){
            System.out.println("门关上了");
        }
    }
}
public class OpenDoorListener implements DoorListener{
    @Override
    public void doorEvent(DoorEvent doorEvent){
        if(doorEvent.getState() == 1){
            System.out.println("门打开了");
        }
    }
}

三、测试

public static void main(String[] args){
    List<DoorListener> list = new ArrayList<>();
    list.add(new OpenDoorListener());
    list.add(new CloseDoorEvent());
    for(DoorListener listener : list){
        listener.doorEvent(new DoorEvent(-1,-1));
        listener.doorEvent(new DoorEvent(1,1));
    }
}

四、输出结果

门打开了
门关上了