首先还是明确一下设计模式的原则
SOLID原则
-
单一职责原则:软件模块应该只有一个被修改的理由
大部分都会应用于类
Class Car {
private String name;
private String model;
private String year;
public void setName(){}
public void create(){}
public void read(){}
public void update(){}
public void delete(){}
public void calculate(){}
}
上面的类封装了car的逻辑和数据库操作,两个职责
要拆成两个类:
Class Car {
private String name;
private String model;
private String year;
public void calculate(){}
}
Class CarDao{
public void setName(){}
public void create(){}
public void read(){}
public void update(){}
public void delete(){}
}
2. 开闭原则:模块、类和函数应该对扩展开放,对修改关闭
开发完后不要动那个函数,解决方法:多态扩展、继承
3. 里氏替换原则(LSP):子类(派生类)必须完全可替代其基类
List<String> list = new ArrayList<String>()
4. 接口隔离原则:客户端不应该依赖于它所不需要的接口
5. 依赖倒置原则:高级模块不应该依赖低级模块,两者都应该依赖抽象;
抽象不应该依赖于细节,细节应该依赖于抽象
------------------------------------正文--------------------------------
创建型模式主要用于处理对象的创建问题,分为:
单例、工厂、对象池、原型、建造者模式
一、单例模式
保证一个对象只能创建一个实例,还要提供对实例的全局访问方法,基础例子:
public class Singleton {
//首先有private的实例,static是为了getInstance方法能调用它
private static Singleton instance;
//把构造方法私有化,不让外部调用构造方法,保证只有一个实例
private Singleton(){}
//静态方法返回单一实例,可以在外部直接类名.getInstance()调用
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
//这个类要做的其他事情
public void doSomething(){}
}
调用:
Singleton.getInstance().doSomething();
1. 同步锁单例模式
问题:多线程中,如果实例为空,当两个线程同时调用getInstance(),会发生并发问题
解决方法:
(1)
public static synchronized Singleton getInstance(){}
(2)用synchronized代码块包装if (instance == null)条件,使用时,要提供一个对象来提供锁
public static Singleton getInstance(){
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
2.拥有双重校验锁机制的同步锁单例模式
问题:只有未实例化的情况下,才需要走锁住的代码,而大部分情况都是不需要的。
解决方法:只有在单例模式未实例化的情况下,才能在synchronized 代码块前添加附加条件移动线程安全锁
第一个if判断是为了在singleton对象已经创建的情况下,避免进入同步代码块,提升效率。第二个if判断能拦截除第一个获得对象锁线程以外的线程,当线程A获取到锁并实例化完instance后,即便线程B已经过了第一个判断,在A释放锁后B才获取锁,然后又判断了instance有没有实例化,此时线程A已经实例化过了,B不会再new了
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
3. 无锁的线程安全单例模式
直接在声明的时候就new
public class Singleton {
//把构造方法私有化,不让外部调用构造方法,保证只有一个实例
private Singleton() {}
//首先有private的实例,static是为了getInstance方法能调用它
private static final Singleton instance = new Singleton();
//静态方法返回单一实例,可以在外部直接类名.getInstance()调用
public static Singleton getInstance() {
return instance;
}
//这个类要做的其他事情
public void doSomething() {}
}
4.提前加载和延迟加载
按照实例对象被创建的时机,单例模式可以分为两类:应用开始时创建单例实例,称为提前加载单例实例;getInstance首次被调用时创建,则为延迟加载
无锁线程安全单例模式在早期版本的java中认为是提前加载的,最新java版本中,类只有在使用时才会被加载,所以也是一种延迟加载。
二、工厂模式
Vehicle vehicle = new Car()
上述代码说明了Vehicle和Car之间的依赖关系,假如现在Car要替换为Truck,就要修改为
Vehicle vehicle = new Truck()
不满足开闭原则(修改了写好的代码)和单一职责原则(主类除了固有功能外,还要实例化vehicle对象)
此时就需要工厂模式,工厂模式用于实现逻辑的封装,并通过公共的接口提供对象的实例化服务,在添加新类时只需要少量的修改
1. 简单工厂模式
封装一个simpleFactory类和createProduct()方法,实例化对象时,根据传入参数的不同,创建不同的具体产品并返回,返回的对象被转换为基类
(1)静态工厂模式
public class VehicleFactory {
public enum VehicleType{
Bike,Car,Truck;
}
public static Vehicle create(VehicleType type){
switch (type){
case Bike: return new Bike();
case Car: return new Car();
case Truck: return new Truck();
default: return null;
}
}
}
abstract class Vehicle{}
class Bike extends Vehicle{}
class Car extends Vehicle{}
class Truck extends Vehicle{}
这样factory只负责实例化对象,符合单一职责謮,但是每增加一个vehicle的子类,要对factory进行修改,违反开闭原则,所以:修改使得注册的新类在使用时才会被实例化,有以下两种方式:
(2)使用反射机制注册产品类对象和实例化
public class VehicleFactory {
private Map<String,Class> registeredProducts = new HashMap<>();
//注册新类的方法
public void registerVehicle(String vehicleId, Class vehicleClass){
registeredProducts.put(vehicleId, vehicleClass);
}
public Vehicle create(String type) throws IllegalAccessException, InstantiationException {
Class productClass = registeredProducts.get(type);
return (Vehicle) productClass.newInstance();
}
}
但是,反射机制需要运行时权限,不一定能实现,且性能不高
(3)使用newInstance方法进行类注册的简单工厂模式
首先,Vehicle基类中添加抽象方法:
abstract public Vehicle newInstance();
对于每种产品,去实现这个方法
@Override
public Car newInstance(){
return new Car();
}
工厂类中,修改:
private Map<String,Vehicle> registeredProducts = new HashMap<>();
public void registerVehicle(String vehicleId, Vehicle vehicle){
registeredProducts.put(vehicleId, vehicle);
}
public AbstractProduct create(String vehicleId) {
return registeredProducts.get(vehicleId).newInstance();
}
2. 工厂方法模式
工厂类被抽象化,用于实例化特定产品类的代码转移到实现抽象方法的子类中,这样不需要修改就可以扩展工厂类。
几个概念:
产品接口(基类)
abstract class Vehicle{}
具体产品
class sportCar extends Vehicle{}
class SedanCar extends Vehicle{}
抽象工厂类:
public abstract class VehicleFactory {
protected abstract Vehicle createVehicle(String item);
public Vehicle orderVehicle(String size, String color){
Vehicle vehicle = createVehicle(size);
vehicle.setColor(color);
return vehicle;
}
}
具体工厂类:实现实例化方法
class CarFactory extends VehicleFactory {
@Override
protected Vehicle createVehicle(String size) {
if (size.equals("small")) {
return new SportCar();
} else if (size.equals("large")) {
return new SedanCar();
} else {
return null;
}
}
}
使用,创建工厂类并创建订单:
VehicleFactory carFactory = new CarFactory();
carFactory.orderVehicle("large","blue")
后续:新建工厂类TruckFactory,不修改就可以扩展工厂方法
3. 抽象工厂模式
工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品
举例:
抽象工厂模式不再是创建单一类型的对象,而是创建一系列相关联的对象。如果说工厂方法模式中只包含了一个抽象产品类,那么抽象工厂模式中包含多个抽象产品类
4.工厂模式总结:
总的来说
(1)简单工厂模式适合于类不多且不会新增的情况,静态工厂模式中的一堆switch case适合这种情况(新增一个类需要多加一个case),newInstance中的map也适合(新增一个类,需要去注册到map中)
(2)工厂方法模式适合于单一种类的产品,一次构建的对象只能是一种动物,一种汽车,一种植物,可以在一个产品下新增多种类。当一个工厂就可以完成任务时,退化为简单工厂模式。
(3)抽象工厂模式适合于一次需要多种类的产品,只需要构造一个可以创建多个产品的工厂,然后创建相应对象即可,但对于已经构造出来的工厂,不能构造出这个工厂里没有的类对象。当只有一种产品时,退化为工厂方法模式
三、建造者模式
当需要实例化一个复杂的类,以得到不同结构和不同类内部状态的对象时,我们可以使用不同的类对它们的实例化操作逻辑分别封装,这些类就是建造者(Builder)
实际上就是Builder().build()
角色:产品类、抽象建造者类builder、具体建造者类、导演类(Director)
汽车建造者样例
有一个Car类,不能每新增一种Car,就多写一个构造函数,所以适合用建造者模式
CarBuilder是建造者基类,包含四个抽象方法
两个具体建造者类,ElectricCarBuilder和GasolineCarBuilder,分别实现CarBuilder的所有抽象方法
导演类负责决定调用那个具体建造者类的哪些方法并传入参数即可
不是一个好的可应用的方式,因为还是太具体,对于一个具体类就要用一个具体建造者,不好用,还是要直接使用Builder().build()方便,只需要加上注解@Data即可
四、原型模式
实际上是一种克隆对象的方法,涉及:
抽象原型类:声明clone()方法的基类或接口,简单场景下,不需要这种基类。Object就有clone()方法,而所有的类都继承了Object,所以都有clone方法
具体原型类:实现或扩展clone()方法的类,调用具体类的clone()方法实现克隆一个对象。
五、对象池模式
重用和共享创建成本高昂的对象,包括封装外部资源的对象(创建时会耗费很多资源)
角色:
资源池类:用于封装逻辑的类,保存和管理资源列表
资源类:用于封装资源的类。资源类通常被资源池类引用,因此只要资源池不重新分配,它就不会被回收
客户端:使用资源的类
客户端需要新资源时,向资源池申请:
public Resource acquireResource(){
if (avaliable.size() <= 0){
Resource resource = new Resource();
inuse.add(resource);
return resource;
}else {
return avaliable.remove(0);
}
}
客户端用完后需要释放资源,资源会重新回到资源池
public void releaseResource(Resource resource){
avaiable.add(resource);
}
典型用例:数据库连接池