常见的设计模式讲解

设计模式

  • 设计模式是系统服务设计中针对场景的一种解决方案,可以解决功能逻辑开发中的共性问题

一.单一职责原则

  • 原理定义:一个类应该只有一个发生变化的原因
  • 模拟场景:访问用户=>普通用户=>vip用户
  • 编码实现:ifelse=>判断实现=>不易维护

单一职责原则:每个类只负责自己的事情,而不是变成万能的对方法来说,一个方法也只做一种事,或者一个类型的事。

在这里插入图片描述

改造前:

public class VideoUserService {
    public void serveGrade(String userType){

        if ("VIP用户".equals(userType)){
            System.out.println("VIP用户,视频1080P蓝光");
        } else if ("普通用户".equals(userType)) {
            System.out.println("普通用户,视频720P超清");
        } else if ("访客用户".equals(userType)) {
            System.out.println("访客用户,视频480P高清");
        }
    }
}


public class ApiTest {
    public static void main(String[] args) {
        VideoUserService service = new VideoUserService();
        service.serveGrade("VIP用户");
        service.serveGrade("普通用户");
        service.serveGrade("访客用户");
    }
}

改造后:

//接口,定义方法
public interface IVideoUserService {
    //清晰度
    void definition();
    //广告
    void advertisement();
}

//实现类 访客用户,
public class GuestVideoUserService implements IVideoUserService {
    @Override
    public void definition() {
        System.out.println("访客用户,视频480P高清");
    }
    @Override
    public void advertisement() {
        System.out.println("访客用户视频有广告");
    }
}

//普通用户
public class OrdinaryVideoUserService implements IVideoUserService {
    @Override
    public void definition() {
        System.out.println("普通用户,视频720P超清");
    }

    @Override
    public void advertisement() {
        System.out.println("普通用户,有广告");
    }
}

//VIP用户
public class VipVideoUserService implements IVideoUserService {
    @Override
    public void definition() {
        System.out.println("VIP用户,视频1080P蓝光");
    }

    @Override
    public void advertisement() {
        System.out.println("VIP用户无广告");
    }
}

二.开闭原则

  • 开闭原则:扩展开放,修改封闭,
  • 面积计算:长方形-三角形-圆形
  • 编码实现:破坏方法=继承扩展

1. 拓展新类而不是修改旧类

2. 开闭原则规则“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”

3.在不破坏原有功能逻辑的基础上,继承类重写其中的方法,做自己想做的修改

计算面积接口:
    public interface CalculationArea {
    /**
     * 长方形
     * x 长
     * y 宽
     * @return 面积
     */
    double rectangle(double x,double y);

    /**
     * 三角形
     * x 边长
     * y 边长
     * z 边长
     * @return 面积
     * 海伦公式
     * S = √(p(p-a)(p-b)(p-c))
     * p = (a+b+c)/2
     */
    double triangle(double x,double y,double z);

    /**
     * 圆形
     * r 半径
     * @return 面积
     * 圆面积公式
     * S = π * r^2
     */
    double circle(double r);
}

计算面积实现类:
    public class CalculationAreaImpl implements CalculationArea {
        //定义PI的值
    private final static double PI = 3.14D;
		//长方形的面积
    @Override
    public double rectangle(double x, double y) {
        return x * y;
    }
		//三角形的面积
    @Override
    public double triangle(double x, double y, double z) {
        //S = √(p(p - a) (p - b) (p - c))
        //p = (a + b + c) / 2
        double p = (x + y + z) / 2;
        return Math.sqrt(p * (p - x) * (p - y) * (p - z));
    }
		//圆形的面积
    @Override
    public double circle(double r) {
        return PI * r * r;
    }
}

问题:如果PI想更精确的话,需要重新定义PI,那我们就需要扩展继承实现它的需求
    
扩展继承:
    public class CalculationAreaExt extends CalculationAreaImpl {
    private final static double PI = 3.141592653D;

    @Override
    public double circle(double r) {
        return PI * r * r;
    }
}

三.里氏替换原则

1. 继承父类而不去改变父类

  • 里氏替换原则:兼容性,维护性,扩展性

  • 银行卡:信用卡,储蓄卡,地铁卡,饭卡

  • 编码实现:银行卡类,正向,逆向

    在这里插入图片描述

2. 里氏替换原则,继承必须确保超类所有的性质在子类中依然成立

迪米特法则原则

1. 一个对象应该对其他对象保持最少的了解。
2. 类与类关系越密切,耦合度越大。
3. 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息。
4. 迪米特法则还有一个更简单的定义: 只与直接的朋友通信。
5. 直接的朋友: 每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现在成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

迪米特法则:

  • 意义在于降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。

单例模式

  1. 单例模式的主要角色:
    • 单例类:只能创建一个实例对象
    • 访问类:使用单例类

单例模式的实现

  1. 分为两类:

    • 饿汉式:类加载就会导致该实例对象创建,会造成内存的浪费

      public class Singleton {
          //创建当前类型的属性
      	private static final Singleton instence = new Singleton();
      	 //避免类在外部被实例化
          private Singleton() {}
          //创建对外提供的方法
      	public Singleton getInstence() {
      		return instence;
      	}
      }
      
    • 懒汉式:类加载不会导致该单例对象被创建,而是首次使用该对象时才会创建

      双重检查锁:
      该方法只是在synchronized代码块的方式外层又加了一层if判断,这样的话当已经有实例的情况下就会直接返回实例化并不会进入到if中,也就不会去获取锁。并且在没有实例的情况下如果两个线程都进入到了最外层的if判断,那么在线程A获取到锁并进入第二层if中并实例化对象之后,线程B就不会进入到第二层if,能够确保单例。
      
      public class Singleton {
          
      	private static volatile Singleton instence = null;
          
      	private Singleton() {}
          
      	public static Singleton getInstence() {
      		if(instence == null){
                  synchronized(Singleton.class) {
                      if(instence == null) {
      				instence = new Singleton();	
      				}	
                  }
              }
      			
      		return instence;
      	}
      }
      

简单工厂模式

  1. 三个角色:

    • 工厂类(Factory Class):负责创建具体产品的逻辑。它根据客户端传入的参数或条件来决定创建哪种具体产品,并返回该产品的实例。
    • 抽象产品类(Abstract Product Class):定义了产品的共同属性和方法,它可以是接口或抽象类。
    • 具体产品类(Concrete Product Class):实现了抽象产品类定义的方法,是工厂类所创建的对象。
  2. 优点:

    ​ 是隐藏了对象的创建细节,客户端只需要关心如何使用产品而无需关心其具体创建过程。

    缺点:

    ​ 是当新增加产品时,需要修改工厂类的代码,违反了开闭原则。

// 抽象产品类
interface Product {
    void operation();
}

// 具体产品类A
class ConcreteProductA implements Product {
    @Override
    public void operation() {
        System.out.println("生产A");
    }
}

// 具体产品类B
class ConcreteProductB implements Product {
    @Override
    public void operation() {
        System.out.println("生产B");
    }
}

// 工厂类,根据反射,用类名去选择要实例化的对象
public class SimpleFactory {
    public Product createProduct(Class c) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Product product = (Product) Class.forName(c.getName()).newInstance();
        return product;
    }


// 客户端代码
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        SimpleFactory simpleFactory = new SimpleFactory();
        Product product = simpleFactory.createProduct(ConcreteProductA.class);
        product.operation();
        Product product1 = simpleFactory.createProduct(ConcreteProductB.class);
        product1.operation();
    }
}


工厂方法模式

  1. 主要角色:

    • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
    • 具体工厂:主要实现抽象工厂中的抽象方法,完成具体产品的创建。
    • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
    • 具体产品:实现了抽象产品角色所定义的接口,有具体工厂创建,它同具体工厂一一对应。
  2. 工厂模式的主要优点:

    ​ 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的创建过程。
    在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,符合开闭原则

    工厂模式的缺点:

    ​ 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

// 抽象产品接口
interface Product {
    void use();
}

// 具体产品类A
class ConcreteProductA implements Product {
    @Override
    public void use() {
        System.out.println("使用具体产品A");
    }
}

// 具体产品类B
class ConcreteProductB implements Product {
    @Override
    public void use() {
        System.out.println("使用具体产品B");
    }
}

// 抽象工厂接口
interface Factory {
    Product createProduct();
}

// 具体工厂类A
class ConcreteFactoryA implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

// 具体工厂类B
class ConcreteFactoryB implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建具体工厂A
        Factory factoryA = new ConcreteFactoryA();
        // 通过具体工厂A创建产品A
        Product productA = factoryA.createProduct();
        productA.use(); // 输出:使用具体产品A

        // 创建具体工厂B
        Factory factoryB = new ConcreteFactoryB();
        // 通过具体工厂B创建产品B
        Product productB = factoryB.createProduct();
        productB.use(); // 输出:使用具体产品B
    }
}

抽象工厂模式

1.抽象工厂模式结构

(1)AbstractFactory(抽象工厂):声明一组用于创建一族产品的方法。,每一种方法对应一种产品。

(2)ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成一个产品族。

(3)AbstractProduct(抽象产品):为每种产品声明接口,在抽象产品中声明产品所具有的业务方法。

(4)ConcreteProduct(具体产品):定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。

总结

1.抽象工厂模式隔离了具体类的生成,更换一个具体的工厂变得就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的公共接口,只需要改变具体工厂的实例就可以在某种程度上改变整个软件系统的行为

2.当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象

3.增加新的产品族很方便,无需修改已有系统,符合开闭原则。

4.增加新的产品结构麻烦,需要较大的修改

// 抽象产品A接口
interface ProductA {
    void use();
}

// 具体产品A1
class ConcreteProductA1 implements ProductA {
    @Override
    public void use() {
        System.out.println("使用具体产品A1");
    }
}

// 具体产品A2
class ConcreteProductA2 implements ProductA {
    @Override
    public void use() {
        System.out.println("使用具体产品A2");
    }
}

// 抽象产品B接口
interface ProductB {
    void eat();
}

// 具体产品B1
class ConcreteProductB1 implements ProductB {
    @Override
    public void eat() {
        System.out.println("使用具体产品B1");
    }
}

// 具体产品B2
class ConcreteProductB2 implements ProductB {
    @Override
    public void eat() {
        System.out.println("使用具体产品B2");
    }
}

// 抽象工厂接口
interface AbstractFactory {
    ProductA createProductA();
    ProductB createProductB();
}

// 具体工厂类1
class ConcreteFactory1 implements AbstractFactory {
    @Override
    public ProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public ProductB createProductB() {
        return new ConcreteProductB1();
    }
}

// 具体工厂类2
class ConcreteFactory2 implements AbstractFactory {
    @Override
    public ProductA createProductA() {
        return new ConcreteProductA2();
    }

    @Override
    public ProductB createProductB() {
        return new ConcreteProductB2();
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建具体工厂1
        AbstractFactory factory1 = new ConcreteFactory1();
        // 通过具体工厂1创建产品A1
        ProductA productA1 = factory1.createProductA();
        productA1.use(); // 输出:使用具体产品A1
        // 通过具体工厂1创建产品B1
        ProductB productB1 = factory1.createProductB();
        productB1.eat(); // 输出:使用具体产品B1

        // 创建具体工厂2
        AbstractFactory factory2 = new ConcreteFactory2();
        // 通过具体工厂2创建产品A2
        ProductA productA2 = factory2.createProductA();
        productA2.use(); // 输出:使用具体产品A2
        // 通过具体工厂2创建产品B2
        ProductB productB2 = factory2.createProductB();
        productB2.eat(); // 输出:使用具体产品B2
    }
}

代理模式

  1. 代理模式是指为其他对象提供一种代理,以控制对这个对象的访问,代理对象在客户端,服务端以及目标对象之间起到中介作用。并且是属于一种结构型模式

静态代理

  1. 优点:
    1. 每个代理类只能代理一个固定的目标类,如果目标类会有多个商店或者厂家,那么就需要增加很多这个代理
    2. 如果厂家增加或者修改一个功能,会影响众多的实现类,厂家类和代理类都需要进行一个修改

在这里插入图片描述

//代理类和这个目标类都要走的一件事情,卖手机
public interface PhoneSell {
    //卖手机并返回价钱  amount要的数量
    public Double sell(Double amount);
}

//厂商销售手机
public class Factory implements PhoneSell {

    /**
     * 卖手机,1800一部
     */
    @Override
    public Double sell(Double amount) {
        Double price = amount * 1800;
        return price;
    }
}


//商店购买,代理类只负责中转,不做具体的买卖,但是可以做增强
public class Shop implements PhoneSell {
    //构建一个厂商对象
    private Factory factory = new Factory();
    @Override
    public Double sell(Double amount) {
        //商家转发给厂家卖手机的信息
        double price = factory.sell(amount);
        //商家需要赚钱
        price = price +  price * 0.2;
        // ... 可以做很多事情,都是增强的意思
        
        //返回需要的价钱
        return price;
    }
}

//测试
  public static void main(String[] args) {
        Shop shop = new Shop();
        Double sell = shop.sell(1.0);
        System.out.println("购买需要"+sell+"元");
    }
}

动态代理

JDK动态代理

通过这个java自带的反射包中的类和接口实现动态代理的功能。里面主要有三个类,分别是InvocationHandler,Method,Proxy三个类,通过这个三个类来实现一个动态代理。要求这个jdk动态代理必须要有接口,如果没有则实现不了。

InvocationHandler接口,里面有一个invoke方法,代理类要完成的这个功能就写在这个方法里面
Method类:主要是获取这个目标类中的方法。Method.invoke()实现
Proxy类:核心的对象,创建代理对象,newProxyInstance()创建代理对象

依旧是使用之前的卖手机的场景,需要用到上面的几个对象。原理相当于将之前写死的一个代理类只能对应一个目标类,改成这个代理类变成一个动态的形式,一个目标类想要一个代理类时,可以通过一个模板来实现,而不需要去重新创建多个代理类。jdk动态代理代码实现如下

1,依旧需要一个主接口,用来规范这个目标类和代理类做一样的事

/**
 * @Author: zhenghuisheng
 * @Date: 2022/9/10 17:39
 * 代理类和这个目标类都要走的一件事情,卖手机
 */
public interface PhoneSell {
    //卖手机并返回价钱  amount要的数量
    public Double sell(Double amount);
}

2,依旧需要一个目标对象

/**
 * @Author: zhenghuisheng
 * @Date: 2022/9/10 17:54
 * 厂商销售手机
 */
public class Factory implements PhoneSell {

    /**
     * 卖手机,1800一部
     */
    @Override
    public Double sell(Double amount) {
        Double price = amount * 1800;
        return price;
    }
}

3,和静态代理不一样的是,动态代理不需要具体的代理类,而是通过实现这个 InvocationHandler这个接口来这个jdk的动态代理。会通过外部的传参来确定成为哪个类的动态代理对象,通过这个方法来确定需要代理这个对象的哪个方法。并且会通过这个invoke方法来获取目标类对象里面的方法,然后获取到结果之后对这个类进行一个功能的增强。最后将执行结果返回

/**
 * @Author: zhenghuisheng
 * @Date: 2022/9/10 20:38
 * jdk动态代理,必须实现InvocationHandler这个接口,然后获取到一个jdk动态代理类
 * 主要是为了调用目标方法,实现功能增强
 */
public class ProxyInvocationHandler implements InvocationHandler {

    //目标对象
    private Object target = null;

    //外部实例化时需要传入这个目标对象
    //传入的是谁就给谁创建动态代理

    public ProxyInvocationHandler(Object target){
        this.target = target;
    }

    /**
     * 这个方法就是代理类里面的代理方法
     * @param proxy :代理类,需要外部手动传入需要代理的类
     * @param method : 目标方法,可能一个类里面会有多个方法
     * @param args : 目标方法参数,对应目标方法里面的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object object = null;

        //目标方法执行
        //第一个是目标对象,第二个是目标参数
        //这个目标对象是一个动态的,因此需要外面传入
        object = method.invoke(target,args);

        //在获取到这个目标方法之后,可以对这个目标方法进行一个增强功能
        if (object != null){
            //外部传入这个sell的这个方法,那么执行这个方法的返回值类型为Double
            Double price = (Double)object;
            //手机店获取利润,功能增强
            price = price + price* 0.2;
            object = price;
        }

        return object;
    }
}

4,在外部需要向ProxyInvocationHandler的构造方法中传入目标对象,并且通过这个Proxy类的newProxyInstance方法来获取这个代理类,然后通过这个获取的代理类执行对应的方法。

//1,创建目标对象
PhoneSell factory = new Factory();
//2,创建proxyInvocationHandler对象
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(factory);
//3,创建动态代理对象
/**
 * newProxyInstance(ClassLoader loader,
 *                 Class<?>[] interfaces,
 *                 InvocationHandler h)
 */
//获取目标类类加载器
ClassLoader classLoader = factory.getClass().getClassLoader();
//获取类对应的接口
Class<?>[] interfaces = factory.getClass().getInterfaces();

//通过反射的形式获取代理类
//强行转换成对应的接口
PhoneSell proxy = (PhoneSell)Proxy.newProxyInstance(classLoader, interfaces, proxyInvocationHandler);
//购买数量
Double sellPrice = proxy.sell(4);
System.out.println(sellPrice);

责任链模式

  1. 责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过将请求的发送者和接收者解耦,使多个对象都有机会处理该请求。在责任链模式中,每个处理器(或称为处理者)都包含对下一个处理器的引用,形成一个链条。当请求从链条的起始处发出时,沿着链条依次经过每个处理器,直到找到能够处理该请求的处理器为
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ylik~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值