入坑码农第一天:设计原则与设计模式(一)

六大设计原则

1. 单一职责原则(Single Reposibility Principle)

定义:一个类或者模块只负责完成一个职责,降低耦合,避免屎山成堆。

2. 里氏替换原则(Liskov Substitution Principle)

定义:所有使用父类的地方可以使用子类的对象,其中子类可以扩展父类的功能,但是不能替换子类的功能,如果需要替换父类的功能,建议多用组合(即实现implement或者将父类定义为抽象类),少用继承。

如果继承是为了实现代码复用和方法共享,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类可以添加新方法来扩展功能。

如果继承的目的是为了多态,也就是子类要重新定义父类方法,那么父类应该被定义成抽象类,并定义抽象方法。由于抽象类不会被实例化,则在程序运行中也不会存在子类替换父类所造成的逻辑混乱(压根就没有父类实例)。

3. 依赖倒置原则(Dependence Inversion Principle)

定义:下层模块引入上曾模块的以来,改变原有自上而下的依赖方向。

后续有时间通过例子进行详细说明。

4. 接口隔离原则

定义:建立单一接口,不要建立臃肿庞大的接口,接口要尽量细化,但应尽量少。

一眼看起来比较矛盾,实际上中心思想就是,单一职责、高内聚、低耦合。同时要提高接口、类、模块的处理能力,减少对外的交互,自己只负责做自己的事情,不要把自己的事情没做完,交给另一个模块去做,当然不意味着禁止接口交互,而是自己负责的内容要做的完整利索。这样通过高质量的接口组装,实现服务定制化。

5. 迪米特法则(Law of Demeter)

定义:最少知识原则,一个类应对自己需要耦合和调用的类了解最少,不管你要调用的接口或者类实现的有多复杂,对你来说,他就是一个黑盒子,满足你的需求就行了,其他具体怎么实现的与你无关。

6. 开闭原则

定义:类、方法、模块应该对扩展开发,对修改关闭

即:添加功能应该是在原有代码上面进行扩展,添加新的方法,而不是在原有代码进行一顿修改,改来改去自己都不知道干了个啥,那这个代码质量就很堪忧。

设计模式

一、创建型模式

创建型模型主要关注的是“如何创造对象”,将对象的创建与使用分离开,使用者不需要关注对象的创建细节,降低系统耦合度

单例模式

定义:一个类只有一个实例,并提供全局访问

单例模式共有五种实现方式,相比大家面试过程中也会遇到手写单例,不同单例的实现方式有什么区别,下面我们逐个讲解

饿汉式
public class Singleton1{

    private static singleton1 = new Singleton1();

    // 构造函数私有化
    private Singleton1(){}

    public static Singleton1 getInstance(){
        return singleton1;
    }
}

通过对构造函数私有化来避免其他类对其创建实例,为什么叫饿汉式呢,因为我们没有在获取实例对象的时候,他就已经帮我们初始化对象出来了,而有时候我们希望在使用该对象的时候再让其初始化,即懒汉式。

懒汉式
public class Singleton2 {

    private static Singleton2 instance = null;

    private Singleton2 (){}
    
    //确保只有一个线程可以创建单例
    public static synchronized Singleton2 getInstance(){
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

只有在使用该对象的时候才去创建他,因为我们要保证只有一个线程可以创建单例,所以我们要在getInstance()方法上面加锁确保其唯一。但是由于我们对整个方法上面加了锁,并且只有在第一次获取单例的时候才会执行创建逻辑,而后面每一个线程想要获取单例的时候都要进行加锁,这样不免增加性能消耗,因此我们对上述代码进行优化:

双重检锁
public class Singleton3 {

    private volatile static Singleton3 instance = null;

    private Singleton3 (){}

    public static Singleton3 getInstance(){
        if (instance == null) {
            synchronized(Singleton3.class) {
                if (instance == null){
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

这样我们只在创建对象的时候加锁就解决了上述的性能问题。引入volatile关键字是为了时对象可见,防止在多线程同时进入加锁逻辑时出现问题。

有的时候我们觉得这个代码比较复杂,看着不够优雅,于是我们还可以通过另一种方式来实现

静态内部类
public class Singleton4 {

    private Singleton4 (){}

    private static class SingletonInstance {
        private static final Singleton4 instance = new Singleton4();
    }
        
    public static Singleton4 getInstance(){
        return SingletonInstance.instance;
    }

}

由于静态变量本身就是线程安全的,这是因为,在类加载的最后阶段(类的初始化)会对静态变量进行赋值,这个操作时有javac中的clinit方法帮我们做的,在进行静态变量初始化的过程中会自动在多线程环境下加锁同步,直到这个线程执行完clinit方法后,其他线程才会被唤醒,同时一个类型只会被加载一次。 

静态内部类中的变量在被调用的时候才会取对内部变量进行初始化,因此可以做到延时加载(懒汉式)。

枚举类实现
public enmu Singleton5 {

    INSTANCE;

    public void doSomething(){
        //实现业务逻辑...
    }
}

枚举类天然就是单例的,但是在我们实际开发中几乎不用枚举类处理业务逻辑,更多的是利用其进行code枚举,异常枚举等。 

原型模式

在Java中通过Object方法中的clone()方法或者反序列化来进行对象复制,这种方法就被称为原型模式。

原型模式是在内存中对二进制流进行拷贝,会比直接new一个对象性能好得多,前提是这个对象在初始化的过程中要耗费大量资源或者new一个对象的过程需要非常繁琐的数据准备的情况下原型模式的优点才会被放大

@Data
public class ProtoType implements Cloneable{

    private String name;

    private Integer age;

    private List<String> list = new ArrayList<>();

    public void addData(String data){
        this.list.add(data);
    }

    public ProtoType(){

        System.out.println("执行构造函数");
    }

    public static void main (String[] args) throws CloneNotSupportedException {

        ProtoType type1 = new ProtoType();

        //clone()方法不会执行构造函数
        ProtoType type2 = (ProtoType)type1.clone();

        //false,说明type2是新创建的对象,而并非相同引用
        System.out.println(type1 == type2);

        ProtoType type3 = new ProtoType();
        type3.setAge(18);
        type3.setName("Lucy");
        type3.addData("项链");

        ProtoType type4 = (ProtoType) type3.clone();
        type4.setAge(25);
        type4.setName("Bob");
        type4.addData("眼镜");

        /**
         type3.toString(): ProtoType(name=Lucy, age=18, list=[项链, 眼镜])
         type4.toString(): ProtoType(name=Bob, age=25, list=[项链, 眼镜])
         出现这样的结果表明:如果没有重写clone()方法,那么执行的拷贝是浅拷贝,
         只会复制基本类型和String;
         对于List这种引用类型,clone()方法只复制了对象引用
        **/
        System.out.println(type3);
        System.out.println(type4);

    }
}

从上述例子我们可以了解三点:

1. clone()方法不会执行构造函数

2. 通过clone()方法创建的对象是不同的

3. clone()执行的拷贝是浅拷贝,如果对象存在引用类型,我们需要重写clone()方法

工厂方法模式&抽象工厂模式

在学习工厂方法模式前,我们先了解以下简单工厂

简单工厂

先上代码:

public class SimpleFactory{

    public Car getCar(String type){

        if (type == "BMW") {
            return new BMWCar();
        }
        if (type == "Benz") {
            return new BenzCar();
        }
        if (type == "BYD") {
            return new BYDCar();
        }
        //...
    }

    public static void main(String[] args){
        
        SimpleFactory factory = new SimpleFactory();

        Car benzCar = SimpleFactory.getCar("Benz");
        benzCar.drive();
    }
}

在实例化的时候,有子类来决定创建哪个类的实例对象,其实更像一种编程习惯,在我们日常开发中潜移默化的就养成了这种习惯。接下来我们来说工厂方法:

工厂方法

定义:作为一个创建对象的接口,但有子类决定具体要实例化的是哪一个类,将实例化推迟到子类

抽象工厂

定义:提供接口用于创建相关依赖对象的家族,而不需要明确具体类

定义看起来有点绕,实际我们可以理解成:

工厂方法:一个抽象工厂类可以衍生出多个具体工厂类,每一个具体工厂类可以生产具体实例对象

抽象工厂:抽象工厂是针对多个产品族而言的,即每个工厂可以创建多种不同类型的产品,比如我们要生产一部手机,那么屏幕可以有多个厂商,同样,电池、CPU都可以有多个厂商,可以随意组合。后面有时间可以通过例子进行详细讲解。

建造者模式(生成器模式)

定义:生成器模式可以封装一个产品的构造过程,并允许按步骤构造产品

这种模式可以将复杂对象的创建过程封装起来S

对象通过多个步骤来创建,例如Spring中的DataSourceBuilder

生成器模式与工厂模式相比需要对该对象的生成过程有足够了解,而工厂模式不关注对象具体是怎么产生的

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值