设计模式总结


 

软件设计七大原则

  • 开闭原则:开放扩展、关闭修改。抽象出接口,可实现接口、继承类进行扩展,但尽量不要修改接口。
  • 依赖倒置原则:高层某块不应该依赖低层模块的具体实现,而是依赖低层模块的接口,将耦合度降低到接口层次
  • 单一职责原则:一个类、接口、方法只负责一项功能,降低类的复杂度、变更引起的风险,提高类的可读性,易于维护
  • 接口隔离原则:接口之间相互独立,尽量通过接口来访问
  • 迪米特原则:又叫做最少知道原则,使用其它模块时只关心暴露出来的接口,不关系内部的具体实现
  • 里氏替换原则:父类对象都可以替换为子类对象,即父类适用的地方,子类也应该适用
  • 组合复用原则:尽量用包含来代替继承实现复用,eg. controller要使用某个service,尽量写成成员变量包含进来,而不是继承service

只是参考,根据需求使用

 

设计模式简介

标准的设计模式有23种,简单工厂不属于标准设计模式,但用得也多。

设计模式可分为3类

  • 创建型:用于设计对象的创建方式
  • 结构型:用于设计类的结构、类之间的结构
  • 行为型:用于设计类的行为

 

创建型(1+5种)

简单工厂(不属于标准的设计模式)

一个工厂创建多个类的实例

//写成实例方法、静态方法均可
public class UserFactory{
    public Student getStudent(){
        return new Student();
    }

    public Teacher getTeacher(){
        return new Teacher();
    }
}

优点:简单;缺点:与多个类耦合在一起,不好扩展

工厂都具有的优点:通过工厂创建对象,无需了解具体创建过程

 

工厂方法

一个工厂只创建一个类的实例,使用各自的工厂创建各自的实例

//写成接口、抽象类均可,不要也行
public interface UserFactory{
    User getUser();
}
public class StudentFactory implements UserFactory{
    @Override
    public User getUser() {
        return new Student();
    }
}
public class TeacherFactory implements UserFactory{
    @Override
    public User getUser() {
        return new Teacher();
    }
}

优点:扩展性好;缺点:需要写很多工厂类

 

抽象工厂

顾名思义,对工厂进行抽象,常用于多个对象的组装

//对工厂的抽象
public interface ComputorFactory{
	//多个对象
    Cpu getCpu(String cpu);
    Memory getMemory(String memory);
    Disk getDisk(String disk);
}
public class MacComputorFactory implements ComputorFactory{

    @Override
    public Cpu getCpu() {
        return new Cpu("i5");
    }

    @Override
    public Memory getMemory() {
        return new Memory("ddr3 8g");
    }

    @Override
    public Disk getDisk() {
        return new Disk("ssd 256g");
    }
    
}
public class MacProComputorFactory implements ComputorFactory{

    @Override
    public Cpu getCpu() {
        return new Cpu("i7");
    }

    @Override
    public Memory getMemory() {
        return new Memory("ddr4 16g");
    }

    @Override
    public Disk getDisk() {
        return new Disk("ssd 500g");
    }
    
}

优点:可组装多个不同类型的对象;缺点:在工厂实现中可能有较多的重复代码
 

建造者模式

通过建造者来建造对象的各部分成员,常用于建造成员变量本身比较复杂的对象

@Getter
@Setter
public class Computor {
    private String cpu;
    private String memory;
    private String disk;
}
//建造者
//写成接口、抽象类均可,不要也行
public interface ComputerBuilder{
    void buildCpu(String cpu);
    void buildMemory(String memory);
    void buildDisk(String disk);
    Commputor getComputor();
}
public class ComputerActualBuilder implements ComputerBuilder {
    private Computer computer = new Computor();  //要建造的对象,空参构造器

    @Override
    public void buildCpu(String cpu) {
        computor.setCpu(cpu);  //实际建造时不会这么简单,往往是很复杂的操作
    }

    @Override
    public void buildMemory(String memory) {
        computor.setMemory(memory);
    }

    @Override
    public void buildDisk(String disk) {
        computor.setDisk(disk);
    }
    
    @Override
    public void getComputor() {
        return computor;
    }
    
}
//生产者
@Setter  //提供setter方法注入建造者
class ComputerProducer {
    private ComputerBuilder computerBuilder;  //建造者

    public Computor buildComputer(String cpu, String memory, String disk){
        computerBuilder.buildCpu(cpu);  //开始建造
        computerBuilder.buildMemory(memory);
        computerBuilder.buildDisk(disk);
        return computerBuilder.getComputor();  //返回建造好的对象
    }

}
//使用
ComputerBuilder computerBuilder = new ComputerActualBuilder();  //建造者
ComputerProducer computerProducer = new ComputerProducer();  //生产者
computerAssembler.setComputerBuilder(computerBuilder);  //注入建造者
Computer computer = computerProducer.buildComputor("i7", "ddr4 16g", "ssd 1t");  //生产对象

优点:可建造成员变量本身比较复杂的对象;缺点:编码量大、偏复杂

 

单例模式

常用于构建资源占用多、创建时间开销大的大对象,创建时缓存对象实例,保证该类全局最多只有一个实例,获取到的都是同一个实例,常见的应用比如 线程池、缓存容器、注册表、日志对象等。
 

优点:减少资源占用,提高获取实例的速度。
缺点:使用private 隐藏了构造器,扩展性差;多线程并发访问单例实例时,可能存在线程安全问题。
 

创建单例的2种模式

  • 饿汉式:在类加载时就实例化,本身线程安全
  • 懒汉式:延迟加载,在需要使用实例时才创建实例,多线程环境下可能创建多个实例,需要自行实现单例创建的线程安全
     

饿汉式

public class A {

	//static,类加载时就初始化实例
    private static A a = new A();  
    
    //.....   //其它成员

	//private隐藏构造器
    private A(){  

    }

	//把获取实例的方法暴露出去。只有1步,具有原子性
    public static A getInstance(){  
        return a;
    }

	//....  //其它方法

}

 

懒汉式写法一:线程不安全

//懒汉式  写法一
class A {

    private static A a;

    //.....   

    private A(){

    }

	//多步,不具有原子性,不是线程安全的
    public static A getInstance(){
        if (null == a){  
            a = new A();  
        }
        return a;
    }

    //.....   

}

 

懒汉式写法二:双重校验+加锁,线程安全

class A {

    //volatile禁止指令重排序
    private static volatile A a;

    private A() {

    }

    //多步,不具有原子性,可能发生并发问题,需要加锁
    public static A getInstance() {
        //双重校验(DCL)
        if (null == a) {
        	//加锁
            synchronized (A.class) {
                if (null == a) {
                    a = new A();
                }
            }
        }
        return a;
    }

}
  • 直接用synchronized修饰方法,或直接给外层if加锁,这2种方式性能都比较低,只给a=new A()加锁即可。
  • 最初创建实例时可能有多个线程通过了外层if校验,等待获取类锁,从而出现多次创建实例的问题,一般在创建实例时还需要校验一次,使用双重校验(double check lock)保证只创建一个实例。

 

说明

不管是饿汉式还是懒汉式,反序列化时jvm默认会使用反射创建一个新的对象,这会破坏单例对象的唯一性,所以单例一般不允许序列化。何况为了保证单例本身的线程安全,很多单例类都不使用表示状态的成员字段,没必要序列化。

 

原型模式

创建实例时直接克隆已存在的实例,减少重新创建实例的时间开销

//Object有clone()方法,但不能直接使用,需要实现Cloneable,重写里面的clone()方法才能使用
public class Student implements Cloneable {

    @Override
    protected Object clone() throws CloneNotSupportedException {  //返回类型是Object
        return super.clone();  //调用Object的clone()
    }
    
}
Student student = new Student();
try {
    Student student1 = (Student) student.clone();  //通过克隆来创建实例
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

clone()直接复制二进制流,不调用构造器创建实例。

clone()是浅拷贝,如果有引用型的成员变量,要额外做一些操作实现深拷贝

public class Student implements Cloneable {
    private Teacher teacher;  //引用型成员变量。注意:Teacher类也要实现Cloneable接口,并重写clone()方法

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();  //克隆整个对象
        student.teacher = student.teacher.clone();  //克隆引用型成员,指向新克隆出来的
        return student;
    }
    
}

优点:减少了重新创建实例的时间开销;缺点:有引用型成员变量时可能会写成浅拷贝,从而引发一些问题

 

结构型(7种)

外观模式(门面模式)

向外部提供统一的接口来访问,外部不直接访问具体的业务方法

eg. controller -> service -> dao,将controller作为接口暴露给外部调用,外部不直接访问处理业务的service、dao

优点:无需了解内部具体实现,方便模块间调用;缺点:修改业务方法时,可能会影响暴露出来的接口,维护难度增加

 

装饰者模式

在保留原有功能的基础上进行扩展

//原类
@Getter
public class Computor {
    private String cpu="i7";
    private String memory="8g";
    private String disk="500g ssd";
    private double sum = 6000;  //总价
}
//装饰者。可以使用多个装饰者添加不同的功能
public class ComputorDecorator {
    private Computor computor;  //要被装饰的对象,写成成员变量的形式
    
    private double memoryPrice = 200; //8g内存单价
    private double diskPrice = 500;  //1t机械硬盘单价
    private int memoryCount;  //加装内存条数量
    private int diskCount;  //加装机械硬盘数量,0表示不加装,1表示加装1个
    
    //注入要被装饰的对象
    public ComputorDecorator(Computor computor){  
        this.computor = computor;
    }
  
    //加装一条8g内存
    public void addMemory() {
        if (this.memoryCount==0)
            this.memoryCount = 1;
        else
            System.out.println("只能加装1条");
    }

    //加装1t机械硬盘
    public void addDisk() {
        if (this.diskCount==0)
            this.diskCount = 1;
        else
            System.out.println("只能加装1个机械硬盘");
    }

    public double sum() {  //总价
        return computor.getSum() + memoryPrice * memoryPrice + diskCount * diskPrice;  //在原价的基础上加
    }

}
//用装饰者代替原来的对象来使用
Computor computor = new Computor();
ComputorDecorator computorDecorator = new ComputorDecorator(computor);

computorDecorator.addMemory();  //加装一条8g内存
Double sum = computorDecorator.sum();

优点:继承可以扩展类,但破坏了原类的封装性,装饰者模式提供了一种新的类扩展方式,不会破坏原类的封装

 

适配器模式

把接口、类转换为期待的接口、类,使原本不兼容的接口、类可以协同工作。

 

享元模式

对于频繁使用的大对象,每次都重新创建会很花资源、时间,可以预先创建好一些对象放到集合中,使用时直接从集合中取,用完重置为初始值放回到集合中。

享元模式的常见应用比如线程池、数据库连接池。
 

实现Cloneable接口并重写clone()方法

class B implements Cloneable{

    @Override
    protected B clone() throws CloneNotSupportedException {
        return (B) super.clone();
    }

}

@Getter
@Setter
public class A implements Cloneable{
    private int id;
    private B b;

    @Override
    public A clone() throws CloneNotSupportedException {
        A target = (A)super.clone();
        target.setB(this.getB().clone());  //对引用型成员变量b使用深拷贝
        return target;
    }

}

默认的clone()是浅克隆,如果有引用类型的成员变量,需要自行实现深拷贝,且该引用型成员变量也要是可克隆的(实现Cloneable接口并重写clone()方法)。

Integer、Float等包装类型也算是引用型。

 

组合模式

组合模式用于将多个对象(节点)组合成树形结构,常用于菜单、文件系统、家谱等

//成员
public class Member{
    private String name;  //姓名
    private String gender;  //性别

    public Member(String name, String gender) {
        this.name = name;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public String getGender() {
        return gender;
    }

    //可能要改名
    public void setName(String name) {
        this.name = name;
    }

}
//男性成员,记录老婆、孩子
public class MaleMember extends Member{
    private String wife;  //老婆
    private ArrayList<Member> children = new ArrayList<>();  //孩子

    public MaleMember(String name, String gender) {
        super(name, gender);
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    //添加一个孩子
    public void addChild(Member member){
        children.add(member);
    }

    //获取所有孩子
    public ArrayList<Member> getChildren() {
        return children;
    }
}
//女性成员,要出嫁,只记录丈夫,不记录其子女
public class FemaleMember extends Member{
    private String husband;  //丈夫

    public FemaleMember(String name, String gender) {
        super(name, gender);
    }

    public String getHusband() {
        return husband;
    }

    public void setHusband(String husband) {
        this.husband = husband;
    }

}
//使用
MaleMember ly = new MaleMember("李渊","男");
ly.setWife("窦皇后");  //李渊娶老婆

MaleMember lsm = new MaleMember("李世民","男");  //李世民出生
ly.addChild(lsm); 

lsm.setWife("长孙皇后");  //李世民娶老婆

 

桥接模式

①抽象与具体实现分离,②使用组合代替继承建立类之间的联系

interface UserDao{
    void addUser(User user);
    void deleteUser(int id);
    User findUserById(int id);
    void updateUser(User user);
}

//抽象(接口、抽象类)与具体实现分离

@Repository
class UserDaoImpl implements UserDao{

    @Override
    public void addUser(User user) {

    }

    @Override
    public void deleteUser(int id) {

    }

    @Override
    public User findUserById(int id) {
        return null;
    }

    @Override
    public void updateUser(User user) {

    }
}
interface UserService {
    void addUser(User user);
    void deleteUser(int id);
    User findUserById(int id);
    void updateUser(User user);
}

//抽象(接口、抽象类)与具体实现分离

@Service
class UserServiceImpl implements UserService{
    @Autowired
    private UserDao userDao;  //使用组合代替继承建立类之间的联系

    @Override
    public void addUser(User user) {
        userDao.addUser(user);
    }

    @Override
    public void deleteUser(int id) {
        userDao.deleteUser(id);
    }

    @Override
    public User findUserById(int id) {
        return userDao.findUserById(id);
    }

    @Override
    public void updateUser(User user) {
        userDao.updateUser(user);
    }
}

抽象(接口、抽象类)、实现分离,组合时使用抽象,将耦合降低到抽象层次

 

代理模式

对原类进行增强,用代理代替原类进行工作,常用于aop

参考:https://blog.csdn.net/chy_18883701161/article/details/106509363

 

行为型(11种)

模板方法

将业务划分为多个步骤(方法),在模板方法中调用这些方法,组成完整的业务处理流程。

常用于流程相同、相似的业务处理,可重写模板方法提供不同的处理方式

//招聘
public abstract class Recruit{

    //笔试
    public boolean writtenTest(){

    }

    //面试
    public boolean interview(){

    }

    //发放offer
    public void issueOffer(){

    }

    //模板方法,招聘,在模板方法中调用其它方法,组成完整的业务流程
    public abstract void recruit();

}
//通用招聘,笔试->面试->发放offer
public class CommonRecruit extends Recruit{

    //模板方法
    @Override
    public void recruit(){
        // 先进行笔试
        if (this.writtenTest()) {  
            // 通过则进行面试
            if (this.interview()){  
            	// 通过则发放 offer
                issueOffer();  
            }
        }
    }

}
//内推免笔试,面试->发放offer
class RecommendRecruit extends Recruit{

    //模板方法
    @Override
    public void recruit(){
        //面试
        if (this.interview()==true){  
        	//通过则发放offer
            issueOffer();  
        }
    }

}

 

迭代器模式

用单独的类作为迭代器Iterator来遍历集合,将数据存储、遍历分离,常见的比如jdk自带的集合的迭代器
 

迭代器一般要有2个方法

  • hasNext()  判断是否是最后一个元素(是否还有下一个元素)
  • next()  获取下一个元素

 

策略模式

封装处理同一业务的多种策略,根据需求切换不同的策略即可,不需要了解策略的具体实现

常见的比如spring的7种事务传播策略、数据库的4种隔离级别、线程池的4种拒绝策略、促销方案、出行线路选择(最短时间、最低花费、最少换乘)等

//促销策略接口
public interface SalePromotionStrategy {

    //传入总金额,返回折算后总金额
    double doPromotion(double sum);

}
//策略一:满100减20
class Strategy1 implements SalePromotionStrategy{

    @Override
    public double doPromotion(double sum) {
        if (sum >= 100){
            sum -= 20;
        }else{
            System.out.println("亲,尚不满足满减条件");
        }
        return sum;
    }

}


//策略二:满300减100
class Strategy2 implements SalePromotionStrategy {

    @Override
    public double doPromotion(double sum) {
        if (sum >= 300){
            sum -= 100;
        }else{
            System.out.println("亲,您的购买金额尚未达到300元呢");
        }
        return sum;
    }

}


//策略三:满1000减500
class Strategy3 implements SalePromotionStrategy {

    @Override
    public double doPromotion(double sum) {
        if (sum >= 1000){
            sum -= 500;
        }else{
            System.out.println("亲,您的购物金额尚未达到1000元呢");
        }
        return sum;
    }

}
//使用时,把策略接口写成成员变量,提供setter方法用于注入、修改策略
//直接使用策略即可。无需关心策略的具体实现,修改策略时无需修改其它部分
private SalePromotionStrategy strategy;

public void setSalePromotionStrategy(SalePromotionStrategy strategy) {
    this.strategy = strategy;
}


//结算时使用指定策略计算出优惠后的应付金额
double paySum = this.strategy.doPromotion(sum);

 

解释器模式

将语句按照自定义的方式解释执行

eg. 可以自定义字符串、数字之间的*解释执行规则是:拼接n个相同的字符串, “hello”*2 => “hellohello”

解释器模式基本不用,此处不详细说明

 

观察者模式

对象可以被其它对象观察,当对象被修改时,自动通知所有订阅了该对象的观察者。

事件驱动模型是观察者模式的典型应用,此处以jdk自带的事件驱动模型为例。

eg. 观察一个集合,集合中新增元素时自动通知观察者

import java.util.LinkedList;
import java.util.List;
import java.util.Observable;

/**
 * 被观察的对象,需要继承 Observable
 * Observable中的方法很多都使用 synchronized 修饰,线程安全
 *
 * @param <T> list的元素类型
 */
public class MyList<T> extends Observable {

    private List<T> list = new LinkedList<>();

    /**
     * 获取列表
     *
     * @return 列表
     */
    public List<T> getList() {
        return this.list;
    }

    /**
     * 添加元素
     *
     * @param ele 要添加的元素
     * @return 操作结果
     */
    public boolean add(T ele) {
        boolean result = list.add(ele);
        if (result) {
            //标识状态改变
            this.setChanged();
            //通知所有订阅了此list的观察者。可根据需要传递数据给观察者,不需要传递数据可以调用无参的notifyObservers()
            this.notifyObservers(ele);
        }
        return result;
    }

}
import java.util.Observable;
import java.util.Observer;

/**
 * 观察者,需要实现 Observer 接口,重写 update() 方法
 */
public class MyListObserver implements Observer {

    /**
     * 主题对象发送更新通知时,会自动调用update()方法进行处理
     *
     * @param o   主题对象(被观察者),会自动传入
     * @param arg 主题对象(被观察者)的 notifyObservers() 方法传递的数据
     */
    @Override
    public void update(Observable o, Object arg) {  //被观察的对象、传过来的参数
        MyList myLinkedList = (MyList) o;
        System.out.println("list:" + myLinkedList.getList() + ", 主题对象传递的数据:" + arg);
    }

}
//观察者
MyListObserver myObserver = new MyListObserver();

//主题对象
MyList myLinkedList = new MyList();
//添加观察者。可用多个addObserver()设置多个观察者
myLinkedList.addObserver(myObserver);

//list添加元素,触发更新事件
myLinkedList.add("zhangsan");
myLinkedList.add("lisi");

 

备忘录模式

保存对象状态,后续可恢复对象至某个状态
 

备忘录用得不多,一般只用于特定的开发场景,比如

  • 开发文本编辑器:Ctrl+Z撤销操作、恢复到上一步
  • 开发游戏:进度存档

 
简单说一下

class Article{
    private String title;
    private String content;
    //.....
}
class ArticleStack{
    private Stack<Article> stack = new Stack<>();  
    //.....
}

push对象到栈中,存储对象不同时期的状态,栈是后进先出的,还原时pop()获取上一次的状态进行还原

 

命令模式

将操作封装为命令对象,通过命令对象来执行操作

//命令接口
public interface Command{
    void execute();  //执行命令
}
//命令
//开放CET4、6的报名通道
public class CETApplyOpen implements Command{

    @Override
    public void execute() {
        //....  //开放cet4、6的报名通道
    }

}

//关闭CET4、6的报名通道
public class CETApplyClose implements Command{

    @Override
    public void execute() {
        //....  //关闭cet4、6的报名通道
    }

}


//开放CET4、6的查分通道
public class CETQueryOpen implements Command{

    @Override
    public void execute() {
        //....  //开放cet4、6的查分通道
    }

}

//关闭CET4、6的查分通道
public class CETQueryClose implements Command{

    @Override
    public void execute() {
        //....  //关闭cet4、6的查分通道
    }

}
//执行命令

//把Command作为成员变量,注入要执行的Command对象
private Command command;

//调用execute()执行命令即可
command.execute();

 

中介者模式

对象之间不直接交互,通过中介者来进行交互

eg. 群聊、聊天室,把群聊、聊天室作为中介者,用户之间通过聊天室进行交流

 

责任链模式(职责链模式)

一个请求需要多个操作或对象依次处理,形成一条处理链路,常见的比如拦截器栈

//Handler抽象类
public abstract class Handler{
    protected Handler handler;  //下一个handler。类似链表

    public void setHandler(Handler handler) {  //设置下一个handler
        this.handler = handler;
    }

    public abstract void handler();  //当前handler的处理

}
//Handler的实现类

class Handler1 extends Handler{

    @Override
    public void handler() {
        //....   //当前handler的处理
        if ( this.handler != null){
            this.handler.handler();  //调用下一个handler进行处理
        }
    }

}


class Handler2 extends Handler{

    @Override
    public void handler() {
        //....   //当前handler的处理
        if ( this.handler != null){
            this.handler.handler();  //调用下一个handler进行处理
        }
    }

}
//使用多个handler依次处理请求
Handler1 handler1 = new Handler1();
Handler2 handler2 = new Handler2();
handler1.setHandler(handler2);  //设置下一个handler

//使用handler1接收请求即可

 

访问者模式

根据访问者类型,展示相应的内容,通常要校验用户权限

eg. 有些信息只给vip看,有些信息只给管理员看

 

状态模式

对象存在多种状态,处于不同状态时支持的行为不同。

eg. 订单处于已创建状态时可以付款,处于已付款状态时可以发货,处于已发货状态时可以签收,处于已签收状态时可以进行售后

//订单状态,抽象类
public abstract class OrderStatus{
    private String status;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
//已创建状态
class CreatedOrderStatus extends OrderStatus{

    {
        super.setStatus("created");
    }

    //付款操作
    public void pay(){

    }
}


//已付款状态
class PaidOrderStatus extends OrderStatus{

    {
        super.setStatus("paid");
    }

    //发货操作
    public void send(){

    }
}


//已发货状态
class SentOrderStatus extends OrderStatus{

    {
        super.setStatus("sent");
    }

    //签收操作
    public void receive(){

    }
}


//已签收状态
class ReceivedOrderStatus extends OrderStatus{

    {
        super.setStatus("received");
    }

    //售后操作
    public void afterSale(){

    }
}
class Order{
    private OrderStatus orderStatus = new CreatedOrderStatus();  //使用一个成员来保存状态,状态变化时需要更新此成员
    //.....

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值