侧边栏壁纸
博主头像
叩钉壹刻博主等级

7分技术,3分管理,2分运气

  • 累计撰写 30 篇文章
  • 累计创建 13 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

常用设计模式和七大原则

鹿心肺语
2024-06-20 / 0 评论 / 0 点赞 / 62 阅读 / 18187 字

1. 单例模式

全局唯一实例对象。

单例模式(Singleton Pattern):属于创建型模式,由一个单一的类负责创建自己的对象,确保整个系统中此类的唯一性,类中提供全局访问的示例的方法。

1.1. 单例模式的优点

  1. 全局唯一性,减少内存开销,避免了频繁创建和销毁实例。
  2. 避免资源的多重占用,唯一访问切入点。

1.2. 单例模式的缺点

  1. 没有接口,不能继承。
  2. 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关系实例化方式。

1.3. 简单的单例模式

创建Singleton类,作为单例类。

public class Singleton {
    private static Singleton singleton = null;

    private Singleton() {
        System.out.println("Singleton is created.");
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

    public void doTest() {
        System.out.println("Doing something...");
    }
}

创建Test类,作为测试类。调用单例类。

public class Test {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        singleton1.doTest();
        Singleton singleton2 = Singleton.getInstance();
        singleton2.doTest();
        // 判断singleton1和singleton2是否是同一个对象
        System.out.println(singleton1 == singleton2);
    }
}

注意

  1. 声明Singleton类为私有静态变量。
  2. 提供一个公有静态初始化Singleton类。
  3. 提供所有的操作均为公有属性(public)。

1.4. 多线程安全的单例模式

在多线程方式调用时,为了避免私有静态变量singleton重复初始化,需要对调用getInstance()函数做出控制。具体的解决方法有两种:1. 懒汉模式;2. 饿汉模式

1.4.1. 懒汉模式

创建LazySingleton类,作为懒汉的单例类。

public class LazySingleton {
    private volatile static LazySingleton singleton = null;

    private LazySingleton() {
        System.out.println("LazySingleton is created");
    }

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

    public void doTest() {
        System.out.println("Doing something...");
    }
}

创建Test类,作为测试类。调用单例类。

public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                LazySingleton singleton = LazySingleton.getInstance();
                System.out.println("thread1 singleton's identity hash code: " + System.identityHashCode(singleton));
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                LazySingleton singleton = LazySingleton.getInstance();
                System.out.println("thread2 singleton's identity hash code: " + System.identityHashCode(singleton));
            }
        });
        thread1.start();
        thread2.start();
    }
}

注意

  1. 懒汉模式是等到需要该对象时,才初始化单例对象。
  2. 使用volatile修饰私有静态变量,声明其可见性和禁止指令重排。
  3. 使用synchronized修改初始化LazySingleton对象。

1.4.2. 饿汉模式

创建HungrySingleton类,作为饿汉的单例类。

public class HungrySingleton {
    private static final HungrySingleton singleton = new HungrySingleton();

    public HungrySingleton() {
        System.out.println("HungrySingleton is created.");
    }

    public static HungrySingleton getInstance() {
        return singleton;
    }
}

创建Test类,作为测试类。调用单例类。

public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                HungrySingleton singleton = HungrySingleton.getInstance();
                System.out.println("thread1 singleton's identity hash code: " + System.identityHashCode(singleton));
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                HungrySingleton singleton = HungrySingleton.getInstance();
                System.out.println("thread2 singleton's identity hash code: " + System.identityHashCode(singleton));
            }
        });
        thread1.start();
        thread2.start();
    }
}

注意

  1. 饿汉模式是在使用前进行初始化单例对象。
  2. 相比懒汉模式,实现过程会相对简单。

2. 建造者模式

适用于类的构造参数过多且多数都是可选。

建造者模式(Builder Pattern):属于创建型模式,将复杂对象的创建于表示方式分离,从而创建具有不同表示形式的对象。

2.1. 建造者模式的优点

  1. 分离构建过程和表示,使得建造者相对独立,构建更加灵活。
  2. 建造者使用控制粒度都更细,可以更好地控制构建过程,隐藏具体构建细节。
  3. 代码复用性高,可以在不同地构建过程中重复使用相同的建造者。

2.2. 建造者模式的缺点

  1. 如果使用的属性较少,建造者模式可能会导致代码冗余。
  2. 增加了系统的类和对象数量,实现过程复杂。

2.3. 简单的建造者模式

创建Student类,作为建造者参考和表示对象。

public class Student {

    private String name;
    private int age;
    private String sex;
    private String grade;

    public Student() {

    }

    public Student(String name) {
        this.name = name;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public Student(String name, int age, String sex, String grade) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", grade='" + grade + '\'' +
                '}';
    }
}

创建StudentBuilder类,作为建造者。对Student进行创建。

public class StudentBuilder {
    private String name;
    private int age;
    private String sex;
    private String grade;

    public StudentBuilder setName(String name) {
        this.name = name;
        return this;
    }

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

    public StudentBuilder setSex(String sex) {
        this.sex = sex;
        return this;
    }


    public StudentBuilder setGrade(String grade) {
        this.grade = grade;
        return this;
    }

    public Student build() {
        Student student = new Student();
        student.setName(name);
        student.setAge(age);
        student.setSex(sex);
        student.setGrade(grade);
        return student;
    }
}

创建Test类,作为测试类。调用创建者StudentBuilder类创建Student实例。

public class Test {
    public static void main(String[] args) {
        StudentBuilder studentBuilder = new StudentBuilder();
        Student student = studentBuilder.setName("张三").setAge(18).setSex("男").build();
        System.out.println(student);
    }
}

3. 适配器模式

对目标对象进行不同内容兼容。

适配器模式(Adapter Pattern):属于结构型模式。主要时为了适配不同接口兼容的桥梁。通过中间件(适配器)将一个类的接口转换成另一个期望的接口,使得原本不能一起工作的类能够协同工作。

3.1. 适配器模式的优点

  1. 通过适配器可以透明的调用目标接口。
  2. 提高了类的复用性,最小程度的修改原有代码而重用现有代码。
  3. 提供了良好的灵活性。将目标类和适配者类进行解耦,解决接口不一致问题。

3.2. 适配器模式的缺点

  1. 增加了系统的复杂性。过度使用适配器可能导致系统结构混乱,难以理解和维护。
  2. 增加代码阅读难度,降低代码可读性。

3.3. 简单的适配器模式

创建Medicine类,用作系统药品结构化对象

public class Medicine {
    private String name;
    private String commonName;
    private String specs;

    public Medicine() {
    }

    public Medicine(String name, String specs) {
        this.name = name;
        this.specs = specs;
    }

    public Medicine(String name, String commonName, String specs) {
        this.name = name;
        this.commonName = commonName;
        this.specs = specs;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCommonName() {
        return commonName;
    }

    public void setCommonName(String commonName) {
        this.commonName = commonName;
    }

    public String getSpecs() {
        return specs;
    }

    public void setSpecs(String specs) {
        this.specs = specs;
    }

    @Override
    public String toString() {
        return "Medicine{" +
                "name='" + name + '\'' +
                ", commonName='" + commonName + '\'' +
                ", specs='" + specs + '\'' +
                '}';
    }

}

创建MedicineAdapter类,用于适配其它系统传输过来的数据。

public class MedicineAdapter {
    private Medicine medicine;

    public MedicineAdapter() {
        this.medicine = new Medicine();
    }

    private String getTransName(String medicineInfo) {
        return medicineInfo.split(" ")[0];
    }

    private String getTransCommonName(String medicineInfo) {
        return medicineInfo.split(" ")[1];
    }

    private String getTransSpecs(String medicineInfo) {
        return medicineInfo.split(" ")[2];
    }

    public Medicine getMedicine(String medicineInfo) {
        medicine.setName(getTransName(medicineInfo));
        medicine.setCommonName(getTransCommonName(medicineInfo));
        medicine.setSpecs(getTransSpecs(medicineInfo));
        return medicine;
    }
}

创建Test类,作为测试类。分别调用Medicine类和MedicineAdapter适配者,展示出解构化后的数据。

public class Test {
    public static void main(String[] args) {
        // 直接使用Medicine,由本系统直接可以拆解结构
        Medicine medicine = new Medicine("百忧解", "帕罗西汀", "20mg");
        System.out.println(medicine);
        // 使用MedicineAdapter,由其它系统间接适配本系统的数据结构
        MedicineAdapter medicineAdapter = new MedicineAdapter();
        Medicine medicine1 = medicineAdapter.getMedicine("百忧解 帕罗西汀 20mg");
        System.out.println(medicine1);
    }
}

4. 装饰器模式

丰富类的功能职责,但不改变原有类的结构。

装饰器模式(Decorator Pattern):属于结构型模式,在不改变现有对象结构的情况下,允许给该对象增加一些额外的功能,作为现有类的一个包装。

4.1. 装饰器模式的优点

  1. 低耦合,装饰类和被修饰类可以独立变化,互不影响。
  2. 灵活性,可以在不改变原有对象的情况下,动态地添加或撤销。
  3. 替代继承,提供了继承之外的扩展对象功能的方式,比继承更加灵活。

4.2. 装饰器模式的缺点

  1. 复杂性,多层装饰可能导致系统复杂性增加。

4.3. 简单的装饰器模式

创建Shape接口类,用来抽出所有类的公共方法。

public interface Shape {
    public void draw();
}

创建CircleShape类,实现Shape类。

public class CircleShape implements Shape {
    @Override
    public void draw() {
        System.out.println("Shape: Rectangle");
    }
}

创建RectangleShape类,实现Shape类。

public class RectangleShape implements Shape{
    @Override
    public void draw() {
        System.out.println("Shape: Rectangle");
    }
}

创建ShapeDecorator类,对Shape接口功能进行拓展。

public class ShapeDecorator {
    private Shape shape;

    public ShapeDecorator(Shape shape) {
        this.shape = shape;
    }

    public void draw(String color) {
        this.shape.draw();
        this.setBackgroundColor(color);
    }

    private void setBackgroundColor(String color) {
        System.out.println("set background color is:" + color);
    }

}

创建Test类,作为测试类。调用Shape类的方法和对Shape进行装饰后的ShapeDecorator类的方法。

public class Test {
    public static void main(String[] args) {
        Shape shape = new CircleShape();
        // 原本的类方法
        shape.draw();
        ShapeDecorator shapeDecorator = new ShapeDecorator(shape);
        // 扩展出来附带其它方法
        shapeDecorator.draw("red");
    }
}

5. 代理模式

请求和目标之前的中间层,起到对目标对象的保护。

代理模式(Proxy Pattern):属于结构型模式,通过引入一个代理对象来控制对原有对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。

5.1. 代理模式的优点

  1. 职责分离,低耦性,代理模式将访问控制与业务逻辑分离,在客户端与目标对象之间起到一个中介作用和保护作用。
  2. 扩展性,可以灵活地添加额外的功能或控制,对目标对象的功能进行扩展。
  3. 智能化,可以只能的处理访问请求,如延时加载、缓存等。

5.2. 代理模式的缺点

  1. 性能开销,增加了代理层可能会造成请求处理速度变慢。
  2. 增加系统复杂性,可能会造成系统设计中的类的数量增加。

5.3. 简单的代理模式

创建Service类,用来表示各个服务的请求。

public class Service {

    private String ip;

    private int post;

    public Service() {
    }

    public Service(String ip, int post) {
        this.ip = ip;
        this.post = post;
    }

    public String getIp() {
        return ip;
    }

    public int getPost() {
        return post;
    }

    public String POST() {
        return this.ip + ":" + this.post + "  Service POST";
    }

    public String GET() {
        return this.ip + ":" + this.post + "  Service GET";
    }

    public String PUT() {
        return this.ip + ":" + this.post + "  Service PUT";
    }

    public String DELETE() {
        return this.ip + ":" + this.post + "  Service DELETE";
    }
    
}

创建ServiceProxy类,对Service进行统一代理请求,并增加额外功能,例如:负载均衡、预请求、前置请求和后置请求等。

public class ServiceProxy {
    private List<Service> services = new ArrayList<>();

    public ServiceProxy addService(Service service) {
        services.add(service);
        return this;
    }

    public ServiceProxy addServices(List<Service> service) {
        this.services = service;
        return this;
    }

    public ServiceProxy build() {
        return this;
    }

    private void preRequest() {
        System.out.println("PreRequest");
    }

    private Service balance() {
        int index = new Random().nextInt(this.services.size());
        return services.get(index);
    }

    private void beforeRequest() {
        System.out.println("ServiceProxy beforeRequest");
    }

    private void afterRequest() {
        System.out.println("ServiceProxy afterRequest");
    }

    public String POST() {
        this.preRequest();
        Service service = this.balance();
        this.beforeRequest();
        String response = service.POST();
        System.out.println("response: " + response);
        this.afterRequest();
        return response;
    }

    public String GET() {
        Service service = this.balance();
        this.beforeRequest();
        String response = service.GET();
        System.out.println("response: " + response);
        this.afterRequest();
        return response;
    }

}

创建Test类,作为测试类。调用ServiceProxy类统一请求对应的Service类。

public class Test {
    public static void main(String[] args) {
        ServiceProxy serviceProxy = new ServiceProxy()
                .addService(new Service("127.0.0.1", 8801))
                .addService(new Service("127.0.0.1", 8802))
                .addService(new Service("127.0.0.1", 8803))
                .build();
        // 结果再输出
        System.out.println(serviceProxy.POST());
    }
}

6. 设计模式的七大原则

6.1. 开闭原则

当需求发生变更时,在不修改软件实体的源代码或二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
特点

  • 对软件测试来讲,只对新加入的功能测试,保证软件原有质量。
  • 对代码层面来讲,提高了可复用性和可维护性。

6.2. 里氏替换原则

子类可以扩展父类的功能,但不能改变父类原有的功能。继承过程的规范化。
特点

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类可以拓展自己特有的方法。
  • 子类重载父类的方法时,入参一定要比父类更加宽松。
  • 子类实现父类的方法时,入参一定要比父类更加严格。

6.3. 依赖倒置原则

高层模块不应该依赖低层,两者都应该依赖抽象;抽象不依赖细节,细节应该依赖抽象,要面向接口编程,不要面向实现编程。
特点

  • 降低类之间的耦合性,提高系统的稳定性。
  • 减少并行开发而引发的风险。
  • 提高代码的可读性和可维护性。

6.4. 单一职责原则

一个类应该有且仅有一个引起它变化的原因,否则类应该改被拆分。
特点

  • 降低类的复杂性,提升类的可读性。
  • 提高系统的可维护性,降低由变更而引起的风险。

6.5. 接口隔离原则

客户端不应该被迫依赖于它不使用的方法。
特点

  • 提高系统的灵活性和可维护性,减少项目工程中的代码冗余。
  • 提高系统的内聚程度,保证系统的稳定性。

6.6. 迪米特法则

两个软件实体间没有直接通信时,就不应该直接相互调用,可以通过第三方转发该调用。主要是为了降低类之间的耦合度,提高模块的相对独立性。
特点

  • 降低类之间的耦合度,提高模块的相对独立性。
  • 提高类的复用率和系统的扩展性。
  • 过度使用会使得系统产生大量的中介类。
  • 增加系统的复杂性,降低模块之间的通信效率。

6.7. 合成复用原则

软件在复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
特点

  • 继承复用破坏了类的封装性,也使得父类与子类的耦合度过高。
  • 组合复用既维持了类的封装性,降低新旧类之间的耦合度,有提高了灵活性。
0

评论区