设计模式||

设计模式

面向对象设计原则

单一职责原则

单一职责原则(Simple Responsibility Pinciple,SRP)是最简单的面向对象设计原则,它用于控制类的粒度大小。

一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。

例如:

public class People{
    public void cooking(){
         System.out.println("我会做饭");
    }
    public void teaching(){
         System.out.println("我会教书");
    }
    。。。。
}

People类中功能太多,略显臃肿,应该将各种能力再细分成对应的职业:

public class Cooker{
    public void cooking(){
         System.out.println("我会做饭");
    }
}
public class Teacher{
    public void teaching(){
         System.out.println("我会教书");
    }
}

我们将类的粒度进行更近一步的划分,这样就很清晰了,包括我们以后在设计Mapper、Service、Controller等等,根据不同的业务进行划分,都可以采用单一职责原则,以它作为我们实现高内聚低耦合的指导方针。实际上我们的微服务也是参考了单一职责原则,每个微服务只应担负一个职责。

开闭原则

开闭原则(Open Close Principle)也是重要的面向对象设计原则。

软件实体应当对扩展开放,对修改关闭。

//定义抽象接口
public abstract Code{
    public abstract void coding();
    
    class JavaCoder extends Code(){
        @Override
        public void coding() {
            System.out.println("Java太卷了T_T");
        }
    }
}

抽象类,只用定义这个类要求的行为,具体实现由需要实现他的类自行实现,有较好的扩展性延续性。

里氏替换原则

里氏替换原则(Liskov Substitution Principle)是对子类型的特别定义。它由芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次会议上名为 “数据的抽象与层次” 的演说中首先提出。

所有引用基类的地方必须能透明地使用其子类的对象。

简单的说就是,子类可以扩展父类的功能,但不能改变父类原有的功能:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类可以增加自己特有的方法。
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
  4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样。
public class Father{
    public void play(){
        System.out.println("我会打lol");
    }
    class Son extends Father{
        public void study(){
             System.out.println("我爱学习");
        }
    }
}

Son继承了Father,但是并没有覆盖play方法。符合里氏替换原则。

public class Father{
    public void play(){
        System.out.println("我会打lol");
    }
    class Son extends Father{
        public void study(){
             System.out.println("我爱学习");
        }
        public void play(){
             System.out.println("我爱cs");
        }
    }
}

Son覆盖了父类play,他不再具备父类的行为了,违背了里氏替换原则。

这种情况可以让他们两都继承于一个更高的类People

public abstract class People {

    public abstract void play();   //这个行为还是定义出来,但是不实现
    
    public class Father{
    public void play(){
        System.out.println("我会打lol");
    }
    class Son extends Father{
        public void study(){
             System.out.println("我爱学习");
        }
        public void play(){
             System.out.println("我爱cs");
        }
    }
}
}

依赖倒转原则

依赖倒转原则(Dependence Inversion Principle)也是我们一直在使用的,最明显的就是我们的Spring框架了。

高层模块不应依赖于底层模块,它们都应该依赖抽象。抽象不应依赖于细节,细节应该依赖于抽象。

在之前,编写项目:

public class Main {

    public static void main(String[] args) {
        UserController controller = new UserController();
    }

    static class UserMapper {
        //CRUD...
    }

    static class UserServiceNew {   //由于UserServiceNew发生变化,会直接影响到其他高层模块
        UserMapper mapper = new UserMapper();
        //业务代码....
    }

    static class UserController {   //焯,干嘛改底层啊,我这又得重写了
        UserService service = new UserService();   //哦豁,原来的不能用了
        UserServiceNew serviceNew = new UserServiceNew();   //只能修改成新的了
        //业务代码....
    }
}

使用Spring框架后,使用ServiceImpl来具体实现Service中的方法,Controller直接注入Service接口,这样修改实现也不用修改对应业务的逻辑。

接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP)实际上是对接口的细化。

客户端不应依赖那些它不需要的接口。

电脑和风扇都是电子设备,但是电脑拥有CPU显卡等部件,因此不能单单编写一个Device类让他们两个继承,需要细分为电子设备和普通设备,避免过多不需要的属性堆叠。

合成复用原则

合成复用原则(Composite Reuse Principle)的核心就是委派。

优先使用对象组合,而不是通过继承来达到复用的目的。

可以将B中需要的A类当作B的一个属性 A a,在要用到B的时候进行指定A。

迪米特法则

迪米特法则(Law of Demeter)又称最少知识原则,是对程序内部数据交互的限制。

每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

简单来说就是,一个类/模块对其他的类/模块有越少的交互越好。当一个类发生改动,那么,与其相关的类(比如用到此类啥方法的类)需要尽可能少的受影响(比如修改了方法名、字段名等,可能其他用到这些方法或是字段的类也需要跟着修改)这样我们在维护项目的时候会更加轻松一些。

Test方法需要一个A类的name属性,传入时不传入A类,而是传入String name,在外部Main方法,直接提取A中的name传入Test。

设计模式(创建型)

软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

工厂方法模式

使用简单工厂模式来创建对象

public abstract class Fruit {   //水果抽象类
    private final String name;
    
    public Fruit(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return name+"@"+hashCode();   //打印一下当前水果名称,还有对象的hashCode
    }
}
public class Apple extends Fruit{   //苹果,继承自水果

    public Apple() {
        super("苹果");
    }
}
public class Orange extends Fruit{  //橘子,也是继承自水果
    public Orange() {
        super("橘子");
    }
}

正常情况下,我们直接new就可以得到对象了:

public class Main {
    public static void main(String[] args) {
        Apple apple = new Apple();
        System.out.println(apple);
    }
}

现在我们将对象的创建封装到工厂中:

public class FruitFactory {
    /**
     * 这里就直接来一个静态方法根据指定类型进行创建
     * @param type 水果类型
     * @return 对应的水果对象
     */
    public static Fruit getFruit(String type) {
        switch (type) {
            case "苹果":
                return new Apple();
           	case "橘子":
                return new Orange();
            default:
                return null;
        }
    }
}

现在我们就可以使用此工厂来创建对象了:

public class Main {
    public static void main(String[] args) {
        Fruit fruit = FruitFactory.getFruit("橘子");   //直接问工厂要,而不是我们自己去创建
        System.out.println(fruit);
    }
}

利用对扩展开放,对修改关闭的性质,将简单工厂模式改进为工厂方法模式,那现在既然不让改,那么我们就看看如何去使用扩展的形式:

public abstract class FruitFactory<T extends Fruit> {   //将水果工厂抽象为抽象类,添加泛型T由子类指定水果类型
    public abstract T getFruit();  //不同的水果工厂,通过此方法生产不同的水果
}
public class AppleFactory extends FruitFactory<Apple> {  //苹果工厂,直接返回Apple,一步到位
    @Override
    public Apple getFruit() {
        return new Apple();
    }
}

这样,我们就可以使用不同类型的工厂来生产不同类型的水果了,并且如果新增了水果类型,直接创建一个新的工厂类就行,不需要修改之前已经编写好的内容。

public class Main {
    public static void main(String[] args) {
        test(new AppleFactory()::getFruit);   //比如我们现在要吃一个苹果,那么就直接通过苹果工厂来获取苹果
    }

    //此方法模拟吃掉一个水果
    private static void test(Supplier<Fruit> supplier){
        System.out.println(supplier.get()+" 被吃掉了,真好吃。");
    }
}

这样,我们就简单实现了工厂方法模式,通过工厂来屏蔽对象的创建细节,使用者只需要关心如何去使用对象即可。

建造者模式

经常看到有很多的框架都为我们提供了形如XXXBuilder的类型,我们一般也是使用这些类来创建我们需要的对象。

JavaSE中的StringBulider类。。。

现在有一个学生类:

public class Student {
    int id;
    int age;
    int grade;
    String name;
    String college;
    String profession;
    List<String> awards;

    public Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) {
        this.id = id;
        this.age = age;
        this.grade = grade;
        this.name = name;
        this.college = college;
        this.profession = profession;
        this.awards = awards;
    }
}

他的属性很多,直接构造容易出错。所以,我们现在可以使用建造者模式来进行对象的创建:

public class Student {
		...

    //一律使用建造者来创建,不对外直接开放
    private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) {
        ...
    }

    public static StudentBuilder builder(){   //通过builder方法直接获取建造者
        return new StudentBuilder();
    }

    public static class StudentBuilder{   //这里就直接创建一个内部类
        //Builder也需要将所有的参数都进行暂时保存,所以Student怎么定义的这里就怎么定义
        int id;
        int age;
        int grade;
        String name;
        String college;
        String profession;
        List<String> awards;

        public StudentBuilder id(int id){    //直接调用建造者对应的方法,为对应的属性赋值
            this.id = id;
            return this;   //为了支持链式调用,这里直接返回建造者本身,下同
        }

        public StudentBuilder age(int age){
            this.age = age;
            return this;
        }
      
      	...

        public StudentBuilder awards(String... awards){
            this.awards = Arrays.asList(awards);
            return this;
        }
        
        public Student build(){    //最后我们只需要调用建造者提供的build方法即可根据我们的配置返回一个对象
            return new Student(id, age, grade, name, college, profession, awards);
        }
    }
}

现在,我们就可以使用建造者来为我们生成对象了:

public static void main(String[] args) {
    Student student = Student.builder()   //获取建造者
            .id(1)    //逐步配置各个参数
            .age(18)
            .grade(3)
            .name("小明")
            .awards("ICPC-ACM 区域赛 金牌", "LPL 2022春季赛 冠军")
            .build();   //最后直接建造我们想要的对象
}

单例模式

懒汉式和饿汉式

饿汉式:在使用这个类时自动进行创建对应实例对象。

懒汉式:在使用这个类的单例对象初始化方法时才会创建对象。

饿汉式多线程环境下会有问题!,如果几个线程同时调用,会被创建多次。为了避免这个问题,最简单的是加一个synchronized锁,但是在高并发情况下效率很低。

优化:在一开始进行if判断,判断需要的单例对象是否为空,但是这样也有可能多个线程同时判断为null进入等锁状态

public static Singleton getInstance(){
    if(INSTANCE == null) {
        synchronized (Singleton.class) {
            if(INSTANCE == null) INSTANCE = new Singleton();  //内层还要进行一次检查,双重检查锁定
        }
    }
    return INSTANCE;
}

java中可以使用静态内部类,实现不加锁写法:

public class Singleton {
    private Singleton() {}

    private static class Holder {   //由静态内部类持有单例对象,但是根据类加载特性,我们仅使用Singleton类时,不会对静态内部类进行初始化
        private final static Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){   //只有真正使用内部类时,才会进行类初始化
        return Holder.INSTANCE;   //直接获取内部类中的
    }
}

原型模式

原型模式与对象的拷贝紧密相关

深拷贝和浅拷贝

  • 浅拷贝: 对于类中基本数据类型,会直接复制值给拷贝对象;对于引用类型,只会复制对象的地址,而实际上指向的还是原来的那个对象
  • 深拷贝: 无论是基本类型还是引用类型,深拷贝会将引用类型的所有内容,全部拷贝为一个新的对象,包括对象内部的所有成员变量,也会进行拷贝。
  • 38
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值