适配器模式

适配器就是一种适配中间件,它存在于不匹配的了两者之间,用于连接两者,使不匹配变得匹配。

手机充电需要将220V的交流电转化为手机锂电池需要的5V直流电

知识补充:手机充电器输入的电流是交流,通过变压整流输出电流是直流的。。

类适配器

️2️⃣Adaptee 源角色   :220V的交流电(家用电一般是220V)  

//家用电压
public class HomeElectri {
    //输出220v交流电,AC指的是交流电
    public int outputAC220V() {
        int output = 220;
        return output;
    }
}

1️⃣Target 目标角色 :  手机锂电池需要的5V直流电

目标类只需要定义方法,由适配器来转化:

//手机充电接口
public interface MobileCharge {
    //需要5v的直流电
    //DC指的是直流电
    int outputDC5V();
}

3️⃣Adapter 适配器角色  :手机充电器

我为什么这么写,又去继承又去实现的?

          作为适配器,需要拿到俩边的资源。通过继承拿到源角色的能力(输出220V)通过实现接口,知道自己的目的角色。

//手机的适配器(手机充电器)
public class PhoneAdapter extends HomeElectri implements MobileCharge {

    @Override
    public int outputDC5V() {
        int output = outputAC220V();
        return (output / 44);
    }
}

对象适配器

不继承HomeElectri 类,而是持有HomeElectri 类的实例。

提供一个包装类Adapter,这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API链接起来。

//手机的适配器(手机充电器)
public class PhoneAdapter implements MobileCharge {

    HomeElectri homeElectri;

    public PhoneAdapter(HomeElectri homeElectri) {
        this.homeElectri = homeElectri;
    }

    @Override
    public int outputDC5V() {
        int output = homeElectri.outputAC220V();
        return (output / 44);
    }
}
PhoneAdapter adapter=new PhoneAdapter(new HomeElectri());
System.out.println("输出电压: "+adapter.outputDC5V());


接口适配器

接口的适配器是这样的:接口中往往有多个抽象方法,但是我们写该接口的实现类的时候,必须实现所有这些方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,

如:MouseListener是一个接口,里面有鼠标的各种事件

public interface MouseListener extends EventListener {

    public void mouseClicked(MouseEvent e);

    public void mousePressed(MouseEvent e);

    public void mouseReleased(MouseEvent e);

    public void mouseEntered(MouseEvent e);

    public void mouseExited(MouseEvent e);
}

此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法

MouseAdapter有2个特点

第一:它是抽象类。

第二:每个方法MouseAdapter都做了空实现

public abstract class MouseAdapter implements MouseListener, MouseWheelListener, MouseMotionListener {

    public void mouseClicked(MouseEvent e) {}

    public void mousePressed(MouseEvent e) {}

    public void mouseReleased(MouseEvent e) {}

    public void mouseEntered(MouseEvent e) {}

    public void mouseExited(MouseEvent e) {}

    public void mouseWheelMoved(MouseWheelEvent e){}

    public void mouseDragged(MouseEvent e){}

    public void mouseMoved(MouseEvent e){}
}

而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。

addMouseListener()这个方法需要MouseListener的一个参数

JButton jButton = new JButton("点我有惊喜");
jButton.addMouseListener(new MouseAdapter() {
     @Override
     public void mouseClicked(MouseEvent e) {
         System.out.println("惊不惊喜,意不意外");
     }
});

装饰器模式

装饰模式核心

装饰器的核心就是在不改原有类的基础上给类新增功能。。

1.装饰模式是继承的一个替代模式,通过组合的方式完成继承的功能,但却避免了继承的侵入性

2.装饰类和被装饰类可以独立发展,不会相互耦合


原有类[被装饰]

原有的:

//制作蛋糕
public interface MakCake {
    //cake -蛋糕
    void createCake();
}

它的实现类

public class MakeCakeImpl implements MakCake {
    //制作蛋糕的通用基础逻辑
    @Override
    public void createCake() {
        System.out.println("使用面粉、鸡蛋、牛奶等...制作一个基础蛋糕");
    }
}

抽象装饰器

抽象装饰器(Decorator): 通用的装饰的装饰器,其实现一般是一个抽象类。

被装饰类[MakeCakeImpl]和所有的装饰类【CakeDecorator、FruitAddDecorator、BiscuitAddDecorator ] 必须实现同一个接口[MakCake]

1、而且必须持有被装饰的对象[MakCake的引用],这样,装饰器可以调用MakeCake的方法,也可以选择性的添加自己的行为。

2、可以无限装饰,在运行时为对象添加多个装饰器,从而组合出不同的功能

//制作蛋糕的抽象装饰器
public abstract class CakeDecorator implements MakCake{
    private MakCake makCake;

    public CakeDecorator(MakCake makCake) {
        this.makCake = makCake;
    }

    @Override
    public void createCake() {
        makCake.createCake();
    }
}

装饰器实现的重点是对抽象类继承接口方式的使用,同时设定被继承的接口可以通过构造函数传递其实现类,由此增加扩展性并重写方法里可以实现此部分父类实现的功能。


具体装饰器

具体装饰器(Concrete Decorator):向“制作蛋糕”添加新的职责

给蛋糕添加水果

public class FruitAddDecorator extends AbsCakeDecorator{
    public FruitAddDecorator(MakCake makCake) {
        super(makCake);
    }

    @Override
    public void createCake() {
        super.createCake();
        decorateMethod();
    }

    // 定义自己的修饰逻辑
    private void decorateMethod() {
        System.out.println("加一份新鲜的水果,如草莓...");
    }
}

给蛋糕添加饼干

public class BiscuitAddDecorator extends AbsCakeDecorator {
    public BiscuitAddDecorator(MakCake makCake) {
        super(makCake);
    }

    @Override
    public void createCake() {
        super.createCake();
        decorateMethod();
    }

    // 定义自己的修饰逻辑
    private void decorateMethod() {
        System.out.println("加一份好吃的饼干,如奥利奥...");
    }
}

测试

public static void main(String[] args) {
    //制作最基础的蛋糕面饼
    MakCake cake = new MakeCakeImpl();
    //为蛋糕添加水果
    cake= new FruitAddDecorator(cake);
    cake.createCake();
}

控制台:


无限装饰

什么是无限装饰?使用装饰器模式一层一层的对最底层被包装类进行功能扩展了。 

  • 对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
public static void main(String[] args) {
    //制作最基础的蛋糕面饼
    MakCake cake = new MakeCakeImpl();
    //为蛋糕添加水果
    cake= new FruitAddDecorator(cake);
    // 第二次修饰,蛋糕添加饼干
    cake = new BiscuitAddDecorator(cake);
    cake.createCake();
}

先看结果,打印的顺序和编写代码的顺序是一致的

说实话:看着好像java的缓存流,对基层的io流做了装饰

最后一行调用的时候:cake.createCake()做啥了???

 此时cake是BiscuitAddDecorator的引用

1. 会先调用BiscuitAddDecorator的createCake方法,

2. 走super的createCake(),进入其父类AbsCakeDecorator的方法

3.1 从idea提示看的出此时makCake是FruitAddDecorator的实例(被装饰的对象)那么接着进入FruitAddDecorator的createCake()

3.2.走super的createCake(),再进入AbsCakeDecorator,此时makCake是MakeCakeImpl

的实例

 3.3.进入MakeCakeImpl,控制台打印输出

打印完成后回到3.1,再打印输出

 再回到第一步,BiscuitAddDecorator的createCake(),打印输出

 new 构造器分析

如上,再new装饰器的时候,构造器做啥了 

 //为蛋糕添加水果
 cake= new FruitAddDecorator(cake);

1 进入FruitAddDecorator 

2 AbsCakeDecorator中的makCake赋值为MakeCakeImpl的实例

-----------------------------------

 代码再走一行

 // 第二次修饰,蛋糕添加饼干
 cake = new BiscuitAddDecorator(cake);

3. 进入BiscuitAddDecorator

4.又进入父类AbsCakeDecorator,AbsCakeDecorator中的makCake赋值为FruitAddDecorator的实例


在java哪里见过

Decorator模式的目的就是把一个一个的附加功能,用Decorator的方式给一层一层地累加到原始数据源上,最终,通过组合获得我们想要的功能

例子一:给FileInputStream增加缓冲和解压缩功能,用Decorator模式写出来如下:

// 创建原始的数据源:
InputStream fis = new FileInputStream("test.gz");
// 增加缓冲功能:
InputStream bis = new BufferedInputStream(fis);
// 增加解压缩功能:
InputStream gis = new GZIPInputStream(bis);

BufferedInputStream和GZIPInputStream,它们实际上都是从FilterInputStream继承的,这个FilterInputStream就是一个抽象的Decorator。

Decorator模式有什么好处?它实际上把核心功能和附加功能给分开了。

核心功能指FileInputStream这些真正读数据的源头,附加功能指加缓冲、压缩、解密这些功能。而具体如何附加功能,由调用方自由组合,从而极大地增强了灵活性。

例子二:安全的并发容器类SynchronizedList,比如

List<Object> synchronizedList = Collections.synchronizedList(new ArrayList<>());

Decorator使用场景

场景举例一:

假设我们需要渲染一个HTML的文本,但是文本还可以附加一些效果,比如加粗、变斜体、加下划线等。为了实现动态附加效果,可以采用Decorator模式。

场景举例二:

假设电商平台上有各种商品可以卖,有些商品需要提供定制化的服务,如礼品包装、定制化贺卡等等。这时候可以使用装饰器模式,对商品进行装饰以提供相应服务。

场景举例三:

一个煎饼摊,煎饼可以加鸡蛋、加香肠,计算最终的价格,我们如何实现?


代理模式

代理模式核心

代理模式的特点在于隔离:隔离调用类和被调用类的关系,通过一个代理类去调用。代理模式主要目的是控制对象的访问,并在不改变原对象的前提下增加额外的功能(如权限检查、日志记录),也常用于延迟加载,只有当真正需要的时候才会创建真实对象。


静态代理

被代理对象与代理对象需要实现相同的接口或者是继承相同父类,因此要

定义一个接口或抽象类。

public interface Calculator {
    int add(int i, int j);
}

接口的实现类

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
}

现在想要在add方法执行之前,执行后打印返回结果。做一个增强,很容易想到这么做。。但是一看就不符合开闭原则。。

增加一个代理类,在代理类里打印日志,在代理类里去调用目标对象的方法

public class CalculatorProxy implements Calculator{
    private CalculatorImpl calculatorImpl;

    public CalculatorProxy(CalculatorImpl calculatorImpl) {
        this.calculatorImpl = calculatorImpl;
    }

    @Override
    public int add(int i, int j) {
        System.out.println("[日志] add 方法执行前,参数是:" + i + "," + j);
        int result = calculatorImpl.add(i, j);
        System.out.println("[日志] add 方法结束,运算结果是:" + result);
        return result;
    }
    
}

测试:

 public static void main(String[] args) {
     //创建目标对象
     CalculatorImpl calculator = new CalculatorImpl();
     //创建代理对象
     CalculatorProxy calculatorProxy = new CalculatorProxy(calculator);
     //使用代理对象去调用方法,隔离目标对象和客户端
     calculatorProxy.add(1,3);
 }


jdk动态代理

jdk动态代理是基于接口的一种代理方式,目标对象一定要实现接口。

原理是,利用反射机制,动态生成匿名类继承Proxy类并且实现了要代理的接口

cglib动态代理


策略模式

很多个if...else

通过页面传来的type值,需要返回不同的下拉框内容,我们先用很多个if...else实现。如下:

public class Test {
    public static void main(String[] args) {
        String type = "选择基点";
        String[] conext = getSelectConext(type);
        //打印返回的string数组
        Arrays.stream(conext).forEach(System.out::println);
    }

    public static String[] getSelectConext(String type) {
        if (type.equals("判断条件")) {
            return new String[]{"大于", "小于", "等于", "大于等于", "小于等于"};
        } else if (type.equals("点击方式")) {
            return new String[]{"左键单击", "左键双击", "右键单击", "右键双击"};
        } else if (type.equals("选择基点")) {
            return new String[]{"左上", "左下", "中心", "右上", "右下"};
        }
        return null;
    }
}

缺点:当传入不同的type类型的时候,我们需要继续增加else if。。。


引入策略模式

创建一个接口,该接口针对不同的type。会使用不同的实现类返回对应的内容

//下拉框内容
public interface ComboBoxContent {
    public String[] dropDownlist();
}

创建完成后,就是感觉类有点爆炸(多)。但是可以很好的满足隔离性和扩展性需求,方便承接不断新增的需求

三个实现类

//判断条件
public class JudgeConditionComboBox implements ComboBoxContent{
    @Override
    public String[] dropDownlist() {
        return new String[]{"大于", "小于", "等于", "大于等于", "小于等于"};
    }
}

//---------------------------------------

//点击方式
public class ClickWayComboBox implements ComboBoxContent{
    @Override
    public String[] dropDownlist() {
        return new String[]{"左键单击", "左键双击", "右键单击", "右键双击"};
    }
}

//---------------------------------------

//选择基点
public class BasePointComboBox implements ComboBoxContent{
    @Override
    public String[] dropDownlist() {
        return new String[]{"左上", "左下", "中心", "右上", "右下"};
    }
}

场景一:map结构获取策略

public class ComboBoxStrategyFactory {
    private static final Map<String, ComboBoxContent> map = new HashMap<>();

    static {
        map.put("判断条件", new JudgeConditionComboBox());
        map.put("点击方式", new ClickWayComboBox());
        map.put("选择基点", new BasePointComboBox());
    }

    public static ComboBoxContent getStrategy(String type) {
        return map.get(type);
    }
}

改造之前

----------------------------------

改造后:


场景二:context指定策略

策略模式的控制类[Context]主要是外部可以传递不同的策略实现,再通过统一的方法执行策略计算。

//上下文类
public class ContextComboBox {
    private ComboBoxContent comboBoxContent;

    public ContextComboBox(ComboBoxContent comboBoxContent) {
        this.comboBoxContent = comboBoxContent;
    }

    //真正对外暴露的接口
    public String[] getComboBoxList() {
        //通过不同接口实现类调用具体的策略
        return comboBoxContent.dropDownlist();
    }
}

比如:awt的布局管理器,用户在构建界面的时候指定要使用的布局管理器


观察者模式

观察者模式定义

观察者模式的定义:观察者模式结构中主要包括观察目标(Object)和观察者(Observer)

一个目标对象可被多个观察者对象同时监听,使得每当目标对象状态变化时,所有依赖于它的观察者对象都会得到通知并被自动更新。此种模式通常被用来实时事件处理系统。


Subject 

Subject里面有要通知的观察者们,定义为一个<Observer> observers 。

public class Subject {

    private List<Observer> observers = new ArrayList<Observer>();
    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        // 通知所有的观察者,调用notifyall()方法
        notifyAllObservers();
    }

    public void add(Observer observer) {
        observers.add(observer);
    }

    public void notifyAllObservers() {
        for (Observer observer : observers) {
            // 更新每一个观察者查看的信息
            observer.update();
        }
    }
}

这些观察者都得有update方法,这样在被通知的时候就能去更新了。

怎么保证Observer每一个都有这个方法呢,是不是可以考虑把Observer去继承一个抽象类,里面写一个update()抽象方法,让每一个观察者去继承这个Observer抽象类即可。


Observer

Observer抽象类

public abstract class Observer {
    // 把一个类放进抽象类中意义:其实与普通类中放进去道理一样
    public Subject subject;
    // 定义抽象类,由实现类实现
    public abstract void update();
}

第一个观察者:二进制

//二进制
public class BinaryObserver extends Observer {

    public BinaryObserver(Subject subject) {
        this.subject = subject;
        this.subject.add(this);
    }

    @Override
    public void update() {
        //二进制方式输出数字
        System.out.println("二进制观察者,接收到新的数字: " + subject.getState() + ";并做出对应的处理,处理结果:" + Integer.toBinaryString(subject.getState()));
    }
}

第二个观察者:八进制

//八进制
public class OctalObserver extends Observer {

    public OctalObserver(Subject subject) {
        this.subject = subject;
        this.subject.add(this);
    }

    @Override
    public void update() {
        //八进制方式输出数字
        System.out.println("八进制观察者,接收到新的数字: " + subject.getState() + ";并做出对应的处理,处理结果:" + Integer.toOctalString(subject.getState()));
    }
}

第三个观察者:十六进制

//十六进制
public class HexaObserver extends Observer {

    public HexaObserver(Subject subject) {
        this.subject = subject;
        this.subject.add(this);
    }

    @Override
    public void update() {
        //十六进制方式输出数字
        System.out.println("十六进制观察者,接收到新的数字: " + subject.getState() + ";并做出对应的处理,处理结果:" + Integer.toHexString(subject.getState()));
    }
}

功能测试

public class Test {
    public static void main(String[] args) {
        Subject subject = new Subject();
        //添加二进制观察者
        new BinaryObserver(subject);
        //添加八进制观察者
        new OctalObserver(subject);
        //添加十六进制观察者
        new HexaObserver(subject);

        System.out.println("First state change: 15");
        subject.setState(15);
        System.out.println("------------------------------------------");
        System.out.println("Second state change: 10");
        subject.setState(10);
        System.out.println("------------------------------------------");
        System.out.println("Third state change: 5");
        subject.setState(5);
    }
}


桥接模式

接口是对功能的封装

接口只能做方法声明,不能有方法的实现(接口默认方法实现除外),抽象类中不仅可以作方法声明,也可以做方法实现

d) 抽象类可以有抽象方法、具体的方法和属性,而接口只能有抽象方法和不可变常量。接口里定义的变量只能是公共的静态的常量(public static final),抽象类中的变量是普通变量。

e) 接口是设计的结果,抽象类是重构的结果。抽象类主要用来抽象类别,强调的是所属关系,接口主要用来抽象功能,强调的是特定功能的实现。


桥接模式定义

桥接模式(Bridge Pattern)通过将 抽象 及其 实现 解耦,使二者可以根据需求 独立变化 。

这在以下场景中非常有用:

  • 系统需要支持多种实现方式:例如支付系统中,支付渠道(微信、支付宝)和支付方式(密码、指纹、人脸)需要独立扩展。

  • 系统需要高度可扩展性:未来可能需要添加新的支付渠道或支付方式,而不需要修改现有的代码。

设计模式增加了代码的复杂性,但同时也提高了代码的灵活性和可扩展性。


组合模式

组合模式定义

组合模式(Composite Pattern)又叫作部分整体模式,主要用于实现部分和整体操作的一致性。

透明模式

透明模式:把组合(树节点)使用的方法放到统一行为(Component)中,让不同层次(树节点, 叶子节点)的结构都具备一致行为;

透明组合模式 把所有公共方法都定义在 Component 中:

  • 这样做的好处是客户端无需分辨是叶子节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口;
  • 缺点是叶子节点(Leaf)会继承得到一些它所不需要(管理子类操作的方法)的方法,这与设计模式 接口隔离原则 相违背。

安全模式

安全模式:  统一行为只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加,删除等)放到自身当中;

安全组合模式 把系统各层次公有的行为定义在 Component 中,把组合(树节点)特有的行为(管理子类增加,删除等)放到自身(Composite)中。

  • 这样做的好处是接口定义职责清晰,符合设计模式 单一职责原则 和 接口隔离原则;
  • 缺点是客户需要区分树枝节点(Composite)和叶子节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),违背了设计模式 依赖倒置原则。

责任链模式

责任链模式定义

责任链(Chain of Responsibility)模式,用于避免请求发送者与多个请求处理者耦合在一起,让所有请求的处理者持有下一个对象的引用,从而将请求串联成一条链,在有请求发生时,可将请求沿着这条链传递,直到遇到该对象的处理器。


6大设计原则

1、单一职责原则

单一职责原则:约定一个类应该有且仅有一个改变类的原因。


2、开闭原则

开闭原则规定对扩展开放,对修改封闭。如可以继承父类,扩展需要的方法。


3、里氏替换原则

子类可以扩展父类的功能,但不能改变父类原有的功能


4、迪米特法则

俩个类之间不要有过多的耦合关系,保持最少联动性。


5、接口隔离原则

要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。


6、依赖倒置原则

依赖倒置原则(Dependency Inversion Principle,DIP)的定义可以总结为以下两点:

     a. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。

     b. 抽象不应该依赖于细节;细节应该依赖于抽象。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐