定义是:应该有且仅有一个原因引起类的变更。”
比如我们可以把用户相关的类抽取成一个用户信息BO(Business Object,业务对象),把行为抽取成一个用户信息修改Biz(Business Logic,业务逻辑)
我们也可以把电话的接口抽象为连接管理接口和数据传输接口
但是职责”或变化原因”都是不可度量的。所以实际上受很多因素制约。
接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。”
● 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
● 抽象不应该依赖细节;
● 细节应该依赖抽象。
在Java中,只要定义变量就必然要有类型,一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型,如zhangSan的表面类型是IDriver,实际类型是Driver。
public class Client {
public static void main(String[] args) {
IDriver zhangSan = new Driver();
ICar bmw = new BMW();
//张三开宝马车
zhangSan.drive(bmw);
//张三开奔驰车
ICar benz = new Benz();
zhangSan.drive(benz);
}
}在新增加低层模块时,只修改了业务场景类,也就是高层模块,对其他低层模块如Driver类不需要做任何修改,业务就可以运行,把变更”引起的风险扩散降到最低。
注意 在Java中,只要定义变量就必然要有类型,一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型,如zhangSan的表面类型是IDriver,实际类型是Driver。
依赖倒置对并行开发的影响。两个类之间有依赖关系,只要制定出两者之间的接口(或抽象类)就可以独立开发了,而且项目之间的单元测试也可以独立地运行,而TDD(Test-Driven Development,测试驱动开发)开发模式就是依赖倒置原则的最高级应用。”
司机驾驶汽车的例子,甲程序员负责IDriver的开发,乙程序员负责ICar的开发,两个开发人员只要制定好了接口就可以独立地开发了,甲开发进度比较快,完成了IDriver以及相关的实现类Driver的开发工作,而乙程序员滞后开发,那甲是否可以进行单元测试呢?答案是可以,我们引入一个JMock工具,其最基本的功能是根据抽象虚拟一个对象进行测试,测试类如下所示。
public class DriverTest extends TestCase{
Mockery context = new JUnit4Mockery();
@Test
public void testDriver() {
//根据接口虚拟一个对象
final ICar car = context.mock(ICar.class);
IDriver driver = new Driver();
//内部类”
context.checking(new Expectations(){{
oneOf (car).run();
}});
driver.drive(car);
}
}我们只需要一个ICar的接口,就可以对Driver类进行单元测试。从这一点来看,两个相互依赖的对象可以分别进行开发,孤立地进行单元测试,进而保证并行开发的效率和质量”
抽象是对实现的约束,对依赖者而言,也是一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不脱离契约的范畴,确保约束双方按照既定的契约(抽象)共同发展,只要抽象这根基线在,细节就脱离不了这个圈圈,始终让你的对象做到言必信,行必果”。”
// 1
public interface IDriver {
//是司机就应该会驾驶汽车
public void drive();
}
public class Driver implements IDriver{
private ICar car;
//构造函数注入
public Driver(ICar _car){
this.car = _car;
}
//司机的主要职责就是驾驶汽车
public void drive(){
this.car.run();
}
}
// 2
public interface IDriver {
//车辆型号
public void setCar(ICar car);
//是司机就应该会驾驶汽车
public void drive();”
//车辆型号
public void setCar(ICar car);
//是司机就应该会驾驶汽车
public void drive();
}
public class Driver implements IDriver{
private ICar car;
public void setCar(ICar car){
this.car = car;
}
//司机的主要职责就是驾驶汽车
public void drive(){
this.car.run();
}
}
// 3
public class Driver implements IDriver{
//司机的主要职责就是驾驶汽车
public void drive(ICar car){
car.run();
}
}依赖倒置的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,互不影响,实现模块之间的松耦合。
需要遵循以下几个规则:
接口分为两种:
实例接口:java类也是一种接口,如Person xiaoLi = new Person(); 这个实例遵守的标准就是Person类
类接口:Java中使用interface关键字定义接口
什么是隔离?
即:建立尽可能细化、接口中的方法尽量少的接口
核心观念就是类间解耦,弱耦合,只有弱耦合以后,类的复用率才能提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,为维护带来了难度。使用时需要反复权衡,既做到结构清晰,又要做到高内聚低耦合。
对扩展开放,对修改关闭
注意 我们把价格定义为int类型并不是错误,在非金融类项目中对货币处理时,一般取2位精度,通常的设计方法是在运算过程中扩大100倍,在需要展示时再缩小100倍,减少精度带来的误差。
所有已经投产的代码都有意义,并且受到系统规则的约束,这样的代码不仅逻辑正确,且能保证苛刻条件下不产生有毒代码。因此在变化提出时,需要尽可能不修改原有的代码,仅仅通过扩展实现变化。否则,就需要把原有的测试过程回笼一遍。
所以我们通过扩展实现业务逻辑的变化,而不是修改。单元测试只需要保证新增的方法正确就行。
缩小逻辑粒度,直到一个逻辑不可再拆分为止。
只要扩展一个类,而非修改一个类,是最容易的。如果要读懂原有的复杂代码,再修改容易出问题。
万物皆对象,万物皆运动,有运动就有变化,需要我们在设计之初就考虑到所有可能的变化因素,留下接口,等待“可能”变成“现实”。
通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
增加一个接口IComputerBook和实现类ComputerBook,而BookStore不用任何修改就可以完成书店销售计算机书籍的业务。
首先,ComputerBook类必须实现IBook的3个方法,是通过IComputerBook接口传递进来的约束,即IBook接口对扩展类产生了约束力,正是由于该约束力,BookStore业务类才不需要大量修改
public class BookStore {
private final static ArrayList bookList = new ArrayList();
//static静态模块初始化数据,实际项目中一般是由持久层完成
static{
bookList.add(new NovelBook("天龙八部",3200,"金庸"));
bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
bookList.add(new NovelBook("悲惨世界",3500,"雨果"));
bookList.add(new NovelBook("金瓶梅",4300,"兰陵笑笑生"));
//增加计算机书籍
bookList.add(new ComputerBook("Think in Java",4300,"Bruce Eckel","编程语言"));
}
//模拟书店卖书
public static void main(String[] args) {
NumberFormat formatter = NumberFormat.getCurrencyInstance();
formatter.setMaximumFractionDigits(2);
System.out.println("-----------书店卖出去的书籍记录如下:-----------");
for(IBook book:bookList){
System.out.println("书籍名称:" + book.getName()+"\t书籍作者:" + book.getAuthor()+ "\t书籍价格:" + formatter.format (book.getPrice()/100.0)+"元");
}
}
} 其次,如果原有的程序设计采用的不是接口,而是实现类,如下所示:
private final static ArrayList bookList = new ArrayList(); 一旦这样设计就无法扩展,需要修改原有的业务逻辑(即main方法),这样的扩展就是形同虚设。
最后,如果我们在IBook类增加一个方法getScope,是否可以?不可以!因为原有的实现类NovelBook已经投产运行,它不需要该方法,而且接口是与其他模块交流的契约,修改契约就等于让其他模块修改。因此,接口或抽象类一旦定义,就应该立即执行,不能有改接口的思想。
所以,要实现对扩展开放,首要的前提是抽象约束
什么是元数据?用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。如login方法检查IP地址是否在可以直接访问系统的列表,然后再决定是否需要到数据库中验证密码;又如Spring容器使用的控制反转,在SpringContext配置文件中,通过扩展一个子类,修改配置文件,完成业务变化。
章程中指定了所有人员都必须遵守的约定,对项目来说,约定优于配置。
将相同的变化封装到一个接口或抽象类中
将不同的变化封装到不同的接口或抽象类中,不应该让两个不同的变化出现在同一个接口或抽象类中。
封装变化也就是受保护的变化,封装可能发生的变化。23个设计模式就是从不同的角度对变化进行封装。
确保某一个类只有一个实例,且自行实例化并向整个系统提供这个实例
/Users/cooperniu/Downloads/NoteBook/2022年/设计模式.assets/截屏2022-03-14\ 下午4.03.48.png

单例类的通用代码:
public class Singleton {
private static final Singleton singleton = new Singleton();
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
//类中其他方法,尽量是static
public static void doSomething(){
}
}public class Singleton {
private static Singleton singleton = null;
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,即抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成。
// 抽象产品类
public abstract class Product {
//产品类的公共方法
public void method1(){
//业务逻辑处理
}
//抽象方法
public abstract void method2();
}
// 具体产品类
public class ConcreteProduct1 extends Product {
public void method2() {
//业务逻辑处理
}
}
public class ConcreteProduct2 extends Product {
public void method2() {
//业务逻辑处理
}
}
// 抽象工厂类
public abstract class Creator {
/*
* 创建一个产品对象,其输入参数类型可以自行设置
* 通常为String、Enum、Class等,当然也可以为空
*/
public abstract T createProduct(Class c);
}
// 具体工厂类
public class ConcreteCreator extends Creator {
public T createProduct(Class c){
Product product=null;
try {
product = (Product)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
//异常处理
}
return (T)product;
}
}
// 场景类
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct1.class);
/*
* 继续业务处理
*/
}
} 首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。
再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,“虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
最后,可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。
一个模块只需要一个工厂类,没有必要把它生产出来,使用静态方法就可以了。

// 简单工厂模式中的工厂类
public class HumanFactory {
public static T createHuman(Class c){
//定义一个生产出的人种
Human human=null;
try {
//产生一个人种
human = (Human)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
System.out.println("人种生成错误!");
}
return (T)human;
}
}
// 简单工厂模式中的场景类.HumanFactory类仅有两个地方发生变化:去掉继承抽象类,并在createHuman前增加static关键字;工厂类发生变化,也同时引起了调用者NvWa的变化,
public class NvWa {
public static void main(String[] args) {
//女娲第一次造人,火候不足,于是白色人种产生了
System.out.println("--造出的第一批人是白色人种--");
Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class);
whiteHuman.getColor();
whiteHuman.talk();
//女娲第二次造人,火候过足,于是黑色人种产生了
System.out.println("\n--造出的第二批人是黑色人种--");
Human blackHuman = HumanFactory.createHuman(BlackHuman.class);
blackHuman.getColor();
blackHuman.talk();
//第三次造人,火候刚刚好,于是黄色人种产生了
System.out.println("\n--造出的第三批人是黄色人种--");
Human yellowHuman = HumanFactory.createHuman(YellowHuman.class;
yellowHuman.getColor();
yellowHuman.talk();
}
} 缺点:不容易扩展
当我们在做一个比较复杂的项目时,经常会遇到初始化一个对象很费精力,所有的产品类都放在一个工厂方法进行初始化会使代码不清晰。我们可以为每个产品定义一个创造者,然后由调用者自己去选择与哪个工厂方法关联。
在复杂的应用中一般采用多工厂的方法,然后再增加一个协调类,避免调用者与各个子工厂交流,协调类封装子工厂类,对高层模块提供统一的访问接口。
Singleton定义了一个private的无参构造函数,不允许通过new的方式创建一个对象。那如何建立一个单例对象?通过反射!!
// 负责生成单例的工厂类
public class SingletonFactory {
private static Singleton singleton;
static{
try {
Class cl= Class.forName(Singleton.class.getName());
//获得无参构造
Constructor constructor=cl.getDeclaredConstructor();
//设置无参构造是可访问的
constructor.setAccessible(true);
//产生一个实例对象
singleton = (Singleton)constructor.newInstance();
} catch (Exception e) {
//异常处理
}
}
public static Singleton getSingleton(){
return singleton;
}
}通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一。当然,其他类也可以通过反射的方式建立一个单例对象,确实如此,但是一个项目或团队是有章程和规范的,何况已经提供了一个获得单例对象的方法,为什么还要重新创建一个新对象呢?除非是有人作恶。
以上通过工厂方法模式创建了一个单例对象,该框架可以继续扩展,在一个项目中可以产生一个单例构造器,所有需要产生单例的类都遵循一定的规则(构造方法是private),然后通过扩展该框架,只要输入一个类型就可以获得唯一的一个实例。
一个对象被消费完毕后,不立即释放,工厂类保持其初始状态,等待其再次被使用。

ProductFactory负责产品类对象的创建,通过prMap变量产生一个缓存,对需要再次被重用的对象保留。如果在Map中已经有的对象,直接取出返回;如果没有,则根据需要的类型产生一个对象并放入Map中,以方便下一次调用。
// 延迟初始化的工厂类
public class ProductFactory {
private static final Map prMap = new HashMap();
public static synchronized Product createProduct(String type) throws Exception{
Product product =null;
//如果Map中已经有这个对象
if(prMap.containsKey(type)){
product = prMap.get(type);
}else{
if(type.equals("Product1")){
product = new ConcreteProduct1();
}else{
product = new ConcreteProduct2();
}
//同时把对象放到缓存容器中
prMap.put(type,product);
}
return product;
}
}
延迟加载框架是可以扩展的。如限制某一个产品类的最大实例化数量,可以通过判断Map中的对象数量来实现。如JDBC连接数据库,都要求设置一个MaxConnections最大连接数,即内存中最大实例化的数量。
还可以用在对象初始化比较复杂的情况下,如硬件访问,设计多方面的交互,则可以通过延迟加载降低对象的产生和销毁带来的复杂性。
定义:为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类
// 场景类
// 在场景类中,没有任何一个方法与实现类有关系,对于一个产品来说,我们只要知道它的工厂方法就可以直接产生一个产品对象,无须关心它的实现类
public class Client{
public static void main(String[] args){
AbstractCreator creator1 = new Creator1();
AbstractCreator creator2 = new Creator2();
AbstractProductA a1 = creator1.createProductA();
AbstractProductA a2 = creator2.createProductA();
AbstractProductB b1 = creator1.createProductB();
AbstractProductB b2 = creator2.createProductB();
}
}
封装性
产品族内的约束为非公开状态。例如男女比例问题,抽象工厂里可以有一个约束,每生产一个女性,就同时生产1.2个男性,这样的生产过程对于调用工厂类的高层模块透明,它不需要知道这个约束。具体约束在工厂内实现
产品族扩展非常困难
一个对象族都有相同的约束,则可以使用抽象工厂模式
抽象工厂模式是一个简单的模式,使用的场景非常多,大家在软件产品开发过程中,涉及不同操作系统的时候,都可以考虑使用抽象工厂模式,例如一个应用,需要在三个不同平台(Windows、Linux、Android)上运行,你会怎么设计?分别设计三套不同的应用?非也,通过抽象工厂模式屏蔽掉操作系统对应用的影响。三个不同操作系统上的软件功能、应用逻辑、UI都应该是非常类似的,唯一不同的是调用不同的工厂方法,由不同的产品类去处理与操作系统交互的信息。
有N个产品族,在抽象工厂类中就应该有N个创建方法。有M个产品等级就应该有M个实现工厂类,在每个实现工厂中,实现不同产品族的生产任务。
定义一个操作中的算法的框架,而将一些步骤延迟到子类中实现。使得子类可以不改变一个算法的结构即可重定义该算法的特定步骤。
AbstractClass为抽象模板,方法有两类:
具体模板实现父类所定义的一个或多个抽象方法。
本文由博客一文多发平台 OpenWrite 发布!