一、设计模式的六大原则
- 单一职责原则(SRP):一个类只应该有一个引起它变化的原因
- 开放封闭原则(OCP):软件实体(类、模块、函数等)应该对扩展开发,对修改关闭
- 里氏替换原则(LSP):子类型必须能替换掉它们的父类型
- 依赖倒置原则(DIP):高层模块不应该依赖于底层模块,两者都应该依赖于抽象接口;抽象接口不应该依赖于具体实现,具体实现应该依赖于抽象接口
- 接口隔离原则(ISP):不应该强迫类实现它不需要的接口,应该将接口拆分成更小、更具体的部分,以便客户端只需要直到它感兴趣的部分
- 迪米特法则(LOD):一个对象应该对其他对象尽可能少的了解,通常称“”最少知识原则
二、设计模式三大类
- 创建型模式:这种模式主要关注于如何通过封装对象的创建过程来简化对象的创建,包括单例模式、工厂模式等
- 结构型模式:这种模式主要关注于如何通过简化类与对象之间的关系来增强类的组合能力,包括代理模式、桥接模式等
- 行为型模式:这种模式主要关注于如何通过协调对象的行为来简化软件的设计,包括观察者模式、策略模式等
1、创建型模式
单例模式(singleton pattern)
单例模式的特点:
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给其他所有对象提供这一实例
1)饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这种方式每次都会创建一个对象,容易产生垃圾对象,优缺点也很明显。
优点:没有加锁、效率比较高
缺点:浪费内存
2)懒汉式,双检锁
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
相比于饿汉式每次都new一个对象,懒汉下会判断是否new过对象,没有才会去创建新对象。
总结:单例模式在于确保某个类只有一个实例,并提供一个全局访问点来访问这个实例。在项目中,单例模式适用于日志管理、数据库连接池、线程池、Spring框架中的Bean管理等等。
工厂模式(Factory Pattern)
工厂模式分为三种类型——简单工厂、工厂方法和抽象工厂
1)简单工厂模式
简单工厂模式角色:
- Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有实例的内部逻辑;工厂类可以直接被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它返回一个抽象产品类Product,所有的具体产品都是抽象产品的子类。
- Product(抽象产品角色:抽象产品角色是简单工厂模式所创建的所有对象的父类,负责描述所有实例所共有的公共接口,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个工厂方法,因为所有创建的具体产品对象都是其子类对象。
- ConcreteProduct(具体产品类):具体产品角色是简单工厂模式的创建目标,所有创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现定义在抽象产品中的抽象方法 。
抽象产品角色
public interface Car{
String getColor();
}
具体产品类
public class Su7 implements Car{
@Override
public String getColor() {
return "紫色";
}
}
public class BWM implements Fruit{
@Override
public String getColor() {
return "橙色";
}
}
工厂角色
public class CarFactory {
public Car createCar(String type) {
if(type.equalsIgnoreCase("Su7")){
return new Su7();
} else if(type.equalsIgnoreCase("BWM")) {
return new BWM();
}
return null;
}
}
2)工厂方法模式
工厂方法模式角色:
- 抽象工厂角色(Abstract Factory):具体工厂的公共接口,是具体工厂的父类。
- 具体工厂角色(Concrete Factory):具体工厂,创建产品的实例,供外界调用,主要实现了抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品角色(Product):抽象产品角色是简单工厂模式所创建的所有对象的父类,负责描述所有实例所共有的公共接口。
- 具体产品角色(Concrete Product):每一个具体产品角色都继承了抽象产品角色,需要实现定义在抽象产品中的抽象方法,由具体工厂来创建,它同具体工厂之间一一对应
抽象产品
public interface Car {
String getColor();
}
具体产品
public class Su7 implements Car{
@Override
public String getColor() {
return "紫色";
}
}
public class BWM implements Car{
@Override
public String getColor() {
return "橙色";
}
}
抽象工厂
public interface AbstractFactory {
Car createCar();
}
具体工厂
//豆沙包
public class Su7Factory implements AbstractFactory {
@Override
public Car createCar() {
return new Su7();
}
}
public class BWMFactory implements AbstractFactory {
@Override
public Car createCar() {
return new BWM();
}
}
3)抽象工厂模式
抽象工厂模式各个角色与工厂方法模式类似,下面直接看代码实现
产品
// 抽象产品:轿车
public interface Car {
void showInfo();
}
// 具体产品:轿车A
public class CarA implements Car {
@Override
public void showInfo() {
System.out.println(“This is a CarA.”);
}
}
// 具体产品:轿车B
public class CarB implements Car {
@Override
public void showInfo() {
System.out.println(“This is a CarB.”);
}
}
// 抽象产品:卡车
public interface Truck {
void showInfo();
}
// 具体产品:卡车A
public class TruckA implements Truck {
@Override
public void showInfo() {
System.out.println(“This is a TruckA.”);
}
}
// 具体产品:卡车B
public class TruckB implements Truck {
@Override
public void showInfo() {
System.out.println(“This is a TruckB.”);
}
}
工厂
// 抽象工厂:车辆工厂
public interface VehicleFactory {
Car produceCar();
Truck produceTruck();
}
// 具体工厂:轿车工厂
public class CarFactory implements VehicleFactory {
@Override
public Car produceCar() {
// 某些具体的轿车生产过程
return new CarA();
}
@Override
public Truck produceTruck() {
throw new UnsupportedOperationException("CarFactory does not produce trucks.");
}
}
// 具体工厂:卡车工厂
public class TruckFactory implements VehicleFactory {
@Override
public Car produceCar() {
throw new UnsupportedOperationException(“TruckFactory does not produce cars.”);
}
@Override
public Truck produceTruck() {
// 某些具体的卡车生产过程
return new TruckA();
}
}
总结:工厂方法模式只有一个抽象产品类,抽象工厂模式有多个;工厂方法模式的具体工厂类只能创建一个具体产品类的实例,抽象工厂模式可以创建多个。
2.结构型模式
1.代理模式(Proxy Pattern)
代理模式即通过代理对象访问目标对象,可以在不改变原有目标对象的基础上,对目标对象功能进行拓展。
代理模式分为三种:静态代理、jdk动态代理、cglib动态代理,适用的场景如下:
- 静态代理:代理类必须非常明确,无法做到通过,但效率是最高的
- jdk动态代理:基于接口代理
- cglib动态代理:继承代理对象
- spring aop的默认代理策略:目标对象实现了接口就用jdk代理,否则用cglib代理
- jdk8之后,jdk代理效率比cglib代理高
1)静态代理
目标对象和代理对象需要实现或继承相同父类
/**代理接口*/
public interface IHello {
String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
@Override
public String hi(String key) {
String str = "hello:" + key;
System.out.println("HelloImpl! " + str);
return str;
}
}
/**静态代理类*/
public class HelloStaticProxy implements IHello {
private IHello hello;
public HelloStaticProxy(IHello hello) {
this.hello = hello;
}
@Override
public String hi(String key) {
System.out.println(">>> static proxy start");
String result = hello.hi(key);
System.out.println(">>> static proxy end");
return result;
}
}
/**测试*/
public class DemoTest {
public static void main(String[] args) {
IHello helloProxy = new HelloStaticProxy(new HelloImpl());
helloProxy.hi("world");
}
}
2)jdk动态代理
jdk动态代理是基于接口实现的,所以目标对象一定要实现接口。
/**代理接口*/
public interface IHello {
String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
@Override
public String hi(String key) {
String str = "hello:" + key;
System.out.println("HelloImpl! " + str);
return str;
}
}
/**jdk动态代理类*/
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object target) {
this.target = target;
}
/**
* 获取被代理接口实例对象
*
* @param <T>
* @return
*/
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>> JdkProxy start");
Object result = method.invoke(target, args);
System.out.println(">>> JdkProxy end");
return result;
}
}
/**测试*/
public class Demo2Test {
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy(new HelloImpl());
IHello helloProxy = proxy.getProxy();
helloProxy.hi(" jdk proxy !");
}
}
从代码可看出,jdk代理利用反射机制,动态生成匿名类继承Proxy并实现了要代理的接口
3)cglib动态代理
动态生成class继承目标对象,目标对象可以不实现接口,但不能代理final修饰的类
/**目标类*/
public class ServiceImpl {
public void doSomething() {
System.out.println("do something");
}
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
/**cglib代理类*/
public class CglibProxy {
public static <T> T getProxy(Class<?> clazz) {
// 创建一个 Enhancer 对象。
Enhancer enhancer = new Enhancer();
// 设置要代理的类。
enhancer.setSuperclass(clazz);
// 设置代理逻辑。
enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -> {
System.out.println("before do something");
// 调用原始对象的方法。
Object result = methodProxy.invokeSuper(o, args);
System.out.println("after do something");
return result;
});
// 创建代理对象。
return (T) enhancer.create();
}
}
/**测试*/
public class Main {
public static void main(String[] args) throws Exception {
// 创建一个 Service 对象。
ServiceImpl service = new ServiceImpl();
// 创建一个代理对象。
ServiceImpl proxy = CglibProxy.getProxy(service.getClass());
// 通过代理对象调用方法。
// proxy.getClass().getMethod("doSomething").invoke(proxy);
proxy.doSomething();
}
}
2.桥接模式(Bridge Pattern)
桥接模式是一种将抽象部分和具体实现部分分离,使得它们可以独立变化的一种结构型模式。当一个类内部具有多种变化维度时,使用桥接模式可以解耦这些变化维度,使高层代码架构稳定。桥接模式适用于以下场景:
- 在抽象和具体实现之间需要增加更多灵活性的场景
- 一个类存在多个独立变化的维度,而这些维度都需要独立的去扩展时
- 不希望使用继承,或者多层继承导致系统臃肿
桥接模式的角色组成:
抽象(Abstraction):该类持有一个对Implementor角色的引用,抽象角色的方法需要Implementor角色去实现
修正抽象(RefinedAbstraction):Abstraction的具体实现,对Abstraction中的方法进行完善或扩展
实现(Implementor):确定实现的具体操作,提供给Abstraction使用,一般为接口或抽象类
具体实现(ConcreteImplementor):Implementor类的具体实现
/**
* 抽象 Abstraction
*/
public abstract class Abstraction {
protected IImplementor mImplementor;
public Abstraction(IImplementor implementor) {
this.mImplementor = implementor;
}
public void operation() {
this.mImplementor.operationImpl();
}
}
/**
* 修正抽象 RefinedAbstraction
*/
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(IImplementor implementor) {
super(implementor);
}
@Override
public void operation() {
super.operation();
System.out.println("refined operation");
}
}
/**
* 实现对象 Implementor
*/
public interface IImplementor {
void operationImpl();
}
/**
* 具体实现 ConcreteImplementor
*/
public class ConcreteImplementorA implements IImplementor {
public void operationImpl() {
System.out.println("I'm ConcreteImplementor A");
}
}
/**
* 测试
*/
public class Test {
public static void main(String[] args) {
// 实现角色A
IImplementor imp = new ConcreteImplementorA();
// 抽象角色,聚合实现
Abstraction abs = new RefinedAbstraction(imp);
// 执行操作
abs.operation();
}
}
桥接模式的一个常用场景就是为了替换继承。我们知道继承有很多优点,比如抽象,封装,多态等,父类封装共性,子类实现特性。继承可以很好地帮助我们实现代码复用(封装)的功能,但是同时,这也是继承的一大缺点。因为父类拥有的方法,子类也会继承得到,无论子类是否需要。这说明了继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为,优先使用组合/聚合的方式,而不是继承。
3.行为型模式
1.观察者模式
观察者模式通常由两部分组成——观察者和被观察者,当被观察者状态发生改变时,它会通知所有的观察者对象,使他们能够及时做出响应,所以也被称作“发布-订阅模式”。
角色组成:
- 抽象被观察者(Subject):定义了一个接口,包含了注册观察者、删除观察者、通知观察者等方法。
- 具体被观察者(ConcreteSubject):实现了抽象被观察者接口,维护了一个观察者列表,并在状态发生改变时通知所有注册的观察者。
- 抽象观察者(Observer):定义了一个接口,包含了更新状态的方法。
- 具体观察者(ConcreteObserver):实现了抽象观察者接口,存储了需要观察的被观察者对象,并在被观察者状态发生改变时进行相应的处理。
// 抽象被观察者接口
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 具体被观察者实现类
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherStation() {
this.observers = new ArrayList<>();
}
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
System.out.println(">> 通知所有观察者 <<");
for (Observer observer : observers) {
System.out.println("------观察者:" + observer.name() + "-----------");
observer.update(temperature, humidity, pressure);
}
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
// 抽象观察者接口
interface Observer {
void update(float temperature, float humidity, float pressure);
String name();
}
// 具体观察者实现类
class Display implements Observer {
private float temperature;
private float humidity;
private float pressure;
private String name;
@Override
public String name() {
return this.name;
}
public Display(String name){
this.name = name;
}
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display() {
System.out.println("Temperature: " + temperature);
System.out.println("Humidity: " + humidity);
System.out.println("Pressure: " + pressure);
}
}
// 使用观察者模式实现气象站
public class Main {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
Display display1 = new Display("01");
Display display2 = new Display("02");
weatherStation.registerObserver(display1);
weatherStation.registerObserver(display2);
weatherStation.setMeasurements(25.0f, 60.0f, 1013.0f);
weatherStation.removeObserver(display2);
weatherStation.setMeasurements(26.0f, 65.0f, 1012.0f);
}
}
2.策略模式
使用策略模式可以定义一系列算法,将每个算法封装起来,并使它们可以互换使用。这种模式使得算法可以独立于使用它们的客户端而变化。
interface PaymentStrategy {
void pay(double amount);
}
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
private String cvv;
private String expiryDate;
public CreditCardPayment(String cardNumber, String cvv, String expiryDate) {
this.cardNumber = cardNumber;
this.cvv = cvv;
this.expiryDate = expiryDate;
}
public void pay(double amount) {
System.out.println("Paying " + amount + " using credit card.");
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
private String password;
public PayPalPayment(String email, String password) {
this.email = email;
this.password = password;
}
public void pay(double amount) {
System.out.println("Paying " + amount + " using PayPal.");
}
}
class CashPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("Paying " + amount + " using cash.");
}
}
class PaymentProcessor {
private PaymentStrategy strategy;
public PaymentProcessor(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void processPayment(double amount) {
strategy.pay(amount);
}
}
public class PaymentSystem {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor(new CreditCardPayment("1234 5678 9012 3456", "123", "12/23"));
processor.processPayment(100.0);
processor.setStrategy(new PayPalPayment("example@example.com", "password"));
processor.processPayment(50.0);
processor.setStrategy(new CashPayment());
processor.processPayment(25.0);
}
}
在上面的示例中, PaymentStrategy 接口定义了一种支付方式,并包含一个 pay 方法,该方法接受一个金额参数。我们创建了三个实现该接口的类,分别代表信用卡支付、PayPal支付和现金支付。 PaymentProcessor 类接受一个 PaymentStrategy 实例作为参数,并使用它来执行支付操作。在 main 方法中,我们创建了一个 PaymentProcessor 实例,并使用不同的支付方式来进行支付。