目录
开闭原则
对扩展开放,对修改关闭。
在程序需要进行拓展的时候,不能修改原有的代码。使程序的扩展性好,耦合度低,易于维护和升级。
实现方法:接口和抽象类。当程序需要拓展新的功能时,只需要在原有基础上派生出新的类即可。要求在最初设计抽象类时就进行合理抽象。
依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
简单来说,在设计程序原型时,使用的数据类型(包括成员变量、参数、返回值等)应当是抽象类型,只在实例化时使用具体类。即面向接口编程。
依赖倒转原则是对开闭原则的一个具体描述。
里氏代换原则
任何父类可以出现的地方,子类一定可以出现。子类可以在任何情况下代替父类的位置。
子类可以扩展父类的功能,但不能改变父类原有的功能。这要求子类应当尽量少地对父类的具体实现方法进行重写,尤其减少多态的运用。重写会导致整个体系的复用性变差,特别是多态运用频繁时,程序出错的概率会变大。
接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上。
一个类不应该拥有它不需要的功能,如果一个类的父类/接口拥有这个类不需要的功能,那么这个继承关系/实现关系就是有问题的。
解决方法:对父类进行进一步的抽象,或更细粒度地拆分接口。
最少知识原则
又叫迪米特法则。一个类应当尽可能少地了解其他的类,如果两个类之间能够不直接通信,那么就不应当互相调用,可以通过第三方转发该调用。目的是降低类之间的耦合度。
一般而言,一个类应当尽可能地只与成员对象、方法参数、自己创建的对象等进行通信。
合成复用原则
尽量使用组合或者聚合等关联关系(一个类是另一个类的成员变量)来实现功能,其次再考虑继承关系。
继承的缺点:
组合或聚合复用的优点:
当一个类在程序生命周期内只需要/只能有一个实例时,需要使用单例模式进行设计。
单例模式保证了在任何情况下,这个类最多只有一个实例,任何对该类的创建和访问行为都将指向这个实例。
实现要点:
单例模式分为两种:
实例化在类加载时完成。
public class Singleton {
//在类的内部设置一个成员变量用来存储唯一的实例,需要用static修饰
//因为是饿汉式,此处直接new一个实例
private static Singleton instance = new Singleton();
/*Java语言也可以使用静态代码块赋值
private static Singleton instance;
static {
instance = new Singleton();
}
*/
//私有构造方法
private Singleton() {}
//提供一个外部的访问方法,因为是类方法,所以用static修饰
public static Singleton getInstance() {
return instance;
}
}
饿汉式的方法会在程序加载时就创建单例实例,实例存储在内存中。如果此时程序无须访问这个实例,就会导致内存的浪费。于是可以将懒加载技术引入单例模式,即为懒汉式。
类加载时不进行实例化,实例化在第一次使用该对象时完成。
//“双重检查锁”懒汉式
public class Singleton2 {
//volatile关键字用于禁用Java编译器的指令重排序,保证线程安全
private static volatile Singleton2 instance;
private Singleton2() {}
public static Singleton2 getInstance() {
//在访问对象时使用懒加载
/* 线程不安全的写法:当两个线程同时越过判空语句时,会创建两个实例
if (instance == null) {
instance = new Singleton2();
}*/
//考虑到线程安全问题,需要加锁,使用“双重检查锁”模式
if (instance == null) {//判断部分无须加锁
//只需要锁住实例化的部分即可
synchronized (Singleton2.class) {
//注意此处需要再次检查空指针,防止多个线程同时越过上面的检查
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
}
//“静态内部类”懒汉式
public class Singleton3 {
/*
* 使用一个静态内部类来存储单例
* 内部类必须是静态的,只有静态内部类才可以拥有静态成员变量
* 外部类加载时,内部类并不会被立刻加载,单例也就不会被实例化
* */
private static class SingletonHolder {
public static Singleton3 INSTANCE = new Singleton3();
}
private Singleton3() {}
public static Singleton3 getInstance() {
//在访问对象时会先检查内部类是否被加载,未加载时会先加载内部类,此时单例被实例化
//因为类的加载是单线程的,不会出现线程同步问题
return SingletonHolder.INSTANCE;
}
}
上述单例模式的实现方法存在缺陷,可能会被破坏。
当单例对象通过序列化的方式存储到文件中,再多次读取出来时,一般懒汉式和饿汉式的实现方法所获取的单例对象不是同一个。
可以通过实现readResolve()方法解决。Java反序列化调用readObject()方法时会自动调用该方法。
private Object readResolve() {
//在此处实现getInstance()方法完全相同的功能
}
使用Java自带的反射方法可以破坏单例类的访问控制,然后通过调用构造函数来实例化多个不同对象。
解决方法:在私有的构造函数中加入判断
private Singleton() {
if (instance != null) {
throw new RuntimeException();
}
//如果是静态内部类的方式,则可以设置一个flag变量进行判断
//当然,反射也可以修改flag的值,所以这也并非完全安全,但是无所谓了
}
属于饿汉式的一种特殊实现。是所有单例实现中唯一一种不会被破坏的单例模式。
/*
* 枚举饿汉式
* */
class Singleton {}
enum GetSingleton {
INSTANCE;
private Singleton instance;
//此处的GetSingleton()是默认private的
GetSingleton(){
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
//使用GetSingleton.INSTANCE.getInstance();获取单例对象
}
在面向对象编程中,如果要创建对象时都必须将其new出来,就会导致程序和该对象耦合严重。当需求变化时,就需要将对应的代码都修改一遍,这违背了开闭原则。
工厂模式就是用来解决这一问题。设计一个工厂类来实例化对象,当我们需要一个新对象时,只需要从工厂类中获取即可。
包含以下类型:
//伪代码
//简单工厂类
public class SimpleFactory {
//生产方法可以设置为静态static,可以省去创建工厂对象的步骤
public Item createItem(String type) {
Item item = null;
if (type.equals("type1")) {
item = new ItemType1();
} else if (type.equals("type2")) {
item = new ItemType2();
} else {
throw new RuntimeException("...");
}
return item;
}
}
//========================
//商店类,调用工厂类进行生产
public class Store {
public Item newItem(String type) {
SimpleFactory factory = new SimpleFactory();
Item item = factory.createItem(type);
return item;
}
}
优点:将客户端的业务层逻辑与具体对象的创建解耦合,修改时只需要修改工厂类,降低修改客户端代码的可能性。
缺点:工厂类与具体的对象耦合了,违背了开闭原则。当添加新的产品时,需要修改工厂类的代码。
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品的实例化延迟到其工厂的子类.
包含以下类型:
抽象工厂:提供创建产品的接口供外部调用
具体工厂:实现具体的创建产品的方法
抽象产品:实例的抽象,定义了产品的规范,描述产品的共有特性
具体产品:可实例化的类
//伪代码
//抽象工厂接口
public interface Factory {
//创建对象的方法
Item createItem();
}
//===========================
//具体工厂类1
public class ItemType1Factory implements Factory {
public Item createItem() {
return new ItemType1();
}
}
//具体工厂类2
public class ItemType2Factory implements Factory {
public Item createItem() {
return new ItemType2();
}
}
//===========================
//商店类
public class Store {
private Factory factory;
public void setFactory(Factory factory) {
this.factory = factory;
}
public Item newItem() {
return factory.createItem();
}
}
优点:当添加一种新的产品时,不需要再修改已有的工厂类,只需要新建对应的工厂接口的实现即可。
缺点:每增加一个具体产品就要增加一个产品类和工厂类,复杂度高。
考虑更加复杂的情况:工厂不仅仅生产特定的几种产品,而是需要生产若干组不同的产品组,每一组又包含不同种的产品。如果继续按照上面的工厂模式,则需要大量的产品类和工厂类,出现类爆炸情况。
以汽车厂为例子。工厂既要生产发动机,又要生产车壳等;而汽车厂又分为奔驰汽车场、丰田汽车厂等。
包含以下类型:
抽象工厂:提供多个创建产品的接口供外部调用。抽象的汽车工厂要提供发动机、车壳等多个产品的接口。
具体工厂:实现具体的创建产品的方法,每个产品的方法都需要实现。根据不同商标实现不同的工厂。
抽象产品:与抽象工厂是多对一的关系。有发动机、车壳等产品。
具体产品:可实例化的类。每个品牌的每类产品都有对应的产品。
实现略,没太多意思。
优点:当一组产品需要被同时使用的时候,可以保证程序中同时只出现一套产品。 缺点:当产品种类变得复杂,所有的工厂类都要修改。
先储备一个实例作为原型,当需要一个新对象的时候,复制这个实例。
使用场景:
常常和工厂方法同时使用,工厂通过克隆方法获取新对象,再返回给调用者。
包含以下组件:
浅克隆:创建的新对象中,引用类型的成员变量与原型共享。适用于不做修改的情况。 深克隆:新对象中所有的成员变量都要迭代地克隆,所有零件都是全新的。
实现代码较为简单,略。
将一个复杂对象的构建与表示分离,同样的构建过程可以创建不同的表示。
包含以下组件:
抽象建造者类:Builder接口,规定了要实现复杂对象的哪些部分。
具体建造者类:实现Builder接口,完成各个组件的创建。
产品类:要创建的复杂对象。
指挥者类:负责装配,调用具体建造者来创建复杂对象的各个组件,协调与保证对象各部分完整建造。
在简化的系统下,指挥者类可以和建造者类相结合。
在使用建造者模式的场景中,产品类和建造者类是较为稳定的,业务模式一般封装在指挥者类中以保持系统整体的稳定性。常用于组件变化复杂,但整体的建造过程相对稳定,且组件与建造过程相互独立的业务场景。
举例来说,烹饪蛋炒饭的过程中,先炒蛋再炒饭是一种产品,先炒饭再炒蛋是另一种产品,此时就可以用建造者模式。
//产品和组件类
public class PartA {}
public class PartB {}
public class Product {
private PartA partA;
private PartB partB;
public void setPartA(PartA a) {
this.partA = a;
}
public void setPartB(PartB b) {
this.partB = b;
}
}
//=====================
//抽象建造者类
public abstract class Builder {
//标记为protected,方便子类使用
protected Product product = new Product();
public abstract void bulidPartA();
public abstract void bulidPartB();
public abstract Product createProduct();
}
//具体建造者类
public class BuilderA extends Builder {
//建造
public void bulidPartA() {
this.product.setPartA(new PartA());
}
public void bulidPartB() {
this.product.setPartB(new PartB());
}
public Product createProduct() {
return product;
}
}
//=====================
//指挥者类
public class Director {
//指挥者不直接持有产品,而是通过建造者间接持有产品
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
//调用者最终是通过这个方法获取产品
public Product construct() {
//指挥者类需要协调建造顺序
builder.bulidPartA();
builder.bulidPartB();
return builder.createProduct();
}
}
优点:
缺点:如果不同产品之间工艺流程差异较大,则不适用建造者模式,使用范围受限。
当一个类的构造需要传入很多参数的时候,可能会导致创建类实例的代码可读性差,此时可以用建造者模式进行重构。
public class Product {
private String a;
private String b;
private String c;
/*传统方法需要一个冗长的构造函数
public Product(String a, ...) {...}
*/
//使用建造者模式进行初始化
//注意此处禁止了外部调用构造函数
private Product(Builder builder) {
this.a = builder.a;
this.b = builder.b;
this.c = builder.c;
}
public static final class Builder {
private String a;
private String b;
private String c;
//返回this以方便链式调用
public Builder a(String a) {
this.a = a;
return this;
}
public Builder b(String b) {
this.b = b;
return this;
}
public Builder c(String c) {
this.c = c;
return this;
}
//最终通过这个方法返回新的产品实例
public Product build() {
return new Product(this);
}
}
}
//外部调用的方法
Product product = new Product.Builder()
.a("a")
.b("b")
.c("c")//到此处为止是在构造Builder
.build();//使用Builder创造产品
评论区