1. 单例模式
全局唯一实例对象。
单例模式(Singleton Pattern):属于创建型模式,由一个单一的类负责创建自己的对象,确保整个系统中此类的唯一性,类中提供全局访问的示例的方法。
1.1. 单例模式的优点
- 全局唯一性,减少内存开销,避免了频繁创建和销毁实例。
- 避免资源的多重占用,唯一访问切入点。
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);
}
}
注意:
- 声明Singleton类为私有静态变量。
- 提供一个公有静态初始化Singleton类。
- 提供所有的操作均为公有属性(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();
}
}
注意:
- 懒汉模式是等到需要该对象时,才初始化单例对象。
- 使用volatile修饰私有静态变量,声明其可见性和禁止指令重排。
- 使用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();
}
}
注意:
- 饿汉模式是在使用前进行初始化单例对象。
- 相比懒汉模式,实现过程会相对简单。
2. 建造者模式
适用于类的构造参数过多且多数都是可选。
建造者模式(Builder Pattern):属于创建型模式,将复杂对象的创建于表示方式分离,从而创建具有不同表示形式的对象。
2.1. 建造者模式的优点
- 分离构建过程和表示,使得建造者相对独立,构建更加灵活。
- 建造者使用控制粒度都更细,可以更好地控制构建过程,隐藏具体构建细节。
- 代码复用性高,可以在不同地构建过程中重复使用相同的建造者。
2.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. 适配器模式的优点
- 通过适配器可以透明的调用目标接口。
- 提高了类的复用性,最小程度的修改原有代码而重用现有代码。
- 提供了良好的灵活性。将目标类和适配者类进行解耦,解决接口不一致问题。
3.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. 装饰器模式的优点
- 低耦合,装饰类和被修饰类可以独立变化,互不影响。
- 灵活性,可以在不改变原有对象的情况下,动态地添加或撤销。
- 替代继承,提供了继承之外的扩展对象功能的方式,比继承更加灵活。
4.2. 装饰器模式的缺点
- 复杂性,多层装饰可能导致系统复杂性增加。
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. 代理模式的优点
- 职责分离,低耦性,代理模式将访问控制与业务逻辑分离,在客户端与目标对象之间起到一个中介作用和保护作用。
- 扩展性,可以灵活地添加额外的功能或控制,对目标对象的功能进行扩展。
- 智能化,可以只能的处理访问请求,如延时加载、缓存等。
5.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. 合成复用原则
软件在复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
特点:
- 继承复用破坏了类的封装性,也使得父类与子类的耦合度过高。
- 组合复用既维持了类的封装性,降低新旧类之间的耦合度,有提高了灵活性。
评论区