面向对象开发技术10_00 创建模式-工厂-单例

10_00 创建模式-工厂-单例

1. 设计原则与设计模式

  • 软件开发唯一的真理是“软件一定会变化”。因为你的软件解决的是现实生活中的业务问题,而现实生活中的业务流程总是在不停地变化。

做到OOD的代码需要满足的条件:

  1. 面向对象
  2. 复用
  3. 能以最小的代价满足变化
  4. 不用改变现有代码满足扩展
  • 面向对象的设计原则:提高面向对象设计复用性

设计目标:

  • 设计目可扩展性(Extensibility):新功能易加入系统。
  • 灵活性(Flexibility):允许代码修改平稳发生,不会涉及很多其他模块。
  • 可插入性(Pluggability):容易将一个类换为另一个具有同样接口的类。

软件复用重要性

  • 较高的生产率
  • 较高的软件质量
  • 恰当使用复用,可改善系统的可维护性

面向对象设计

  • 使一个系统可在更高的层次上提供了可复用性
  • 抽象化和继承:使概念和定义可复用
  • 多态:使实现和应用可复用
  • 抽象化和封装:可保持和促进系统的可维护性

复用

  • 抽象层次是一个应用系统作战略性判断和决定的地方,那么抽象层次就应当是较为稳定的,应当是复用的重点
  • 复用的焦点不再集中在函数和算法等具体实现细节上,而是集中在最重要的含有宏观商业逻辑的抽象层次上。
  • 既然如果抽象层次的模块相对独立于具体层次的模块的话,那么具体层次内部的变化就不会影响到抽象层次的结构,所以抽象层次的复用就会较为容易。
  • 面向对象设计中,可维护性复用是以设计原则和设计模式为基础的。

面向对象设计原则(重要)

  1. 开闭原则OCP: Open-Closed Principle
  2. 里氏代换原则LSP: Liskov Substitution Principle
  3. 依赖倒置原则DIP: Dependency Inversion Principle
  4. 接口隔离原则ISP: Interface Segregation Principle
  5. 组合复用原则CRP: Composition Reuse Principle
  6. 迪米特法则LoD: Law of Demeter
  7. 单一职责原则(SRP)

2.开-闭原则OCP

软件组成实体应该是对扩展开放的(可扩展的),但是对修改是关闭的。

  • 开放-封闭法则认为应该试图去设计出永远也不需要改变的模块。
  • 关键在于抽象化:可给系统定义一个一劳永逸,不再更改的抽象设计,此设计允许有无穷无尽的行为在实现层被实现。抽象层预见所有扩展
  • 一个软件系统的所有模块 不可能都满足 OCP,但是应该努力最小化这些不满足OCP的模块数量。

3. 设计模式

  • 设计模式的思想根源是基本原则的宏观运用,本质上是没有任何模式的
  • 设计模式就是实现了基本原则,从而达到了代码复用增加可维护性的目的。

什么叫模式?

  • “每一个模式描述了在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,就能一次又一次地使用该解决方案而不必重复劳动”
  • 尽管软件技术发展非常快,但是仍然有非常多的设计模式可以让我们套用
  • 设计模式可以帮助人们简便地复用以前成功的设计方案,提高工作效率

设计模式的目的?

  • 为了提高代码可重用性、让代码更容易被他人理解、保证代码可靠性。
  • 设计模式使代码编写真正工程化
  • 设计模式是软件工程的基石脉络,如同大厦的结构一样。
  • 学习如何合理地组织我们的代码,如何解耦,如何真正地达到对修改封闭对扩展开放的效果

设计模式基本要素

  • 模式名称(pattern name)
  • 问题( problem)
  • 解决方案( solution)
  • 效果( consequences)

设计模式的分类

  • 创建型:抽象了对象实例化的过程
  • 结构型:如何组合类和对象以获得更大的结构
  • 行为型:描述算法和对象间职责的分配

在这里插入图片描述

4.设计模式-工厂模式(重要)

  1. 简单工厂模式(Simple Factory):不利于产生系列产品;
  2. 工厂方法模式(Factory Method):又称为多形性工厂
  3. 抽象工厂模式(Abstract Factory):又称为工具箱,产生产品族,但不利于产生新的产品
  • 这三种模式从上到下逐步抽象,并且更具一般性。

简单工厂

  • 客户端不但知道了接口,同时还知道了具体的实现就是Impl。
  • 接口的思想是“封装隔离”,而Impl这个实现类,应该是被接口Api封装并同客户端隔离开的,也就是说,客户端不应该知道具体的实现类是Impl

在这里插入图片描述疑惑:什么叫创建实例的类可以是接口、抽象类?

简单工厂的思路

  • 虽然不能让模块外部知道模块内的具体实现,但是模块内部是可以知道实现类的,而且创建接口是需要具体实现类的
  • 模块内部新建一个类,在这个类里面来创建接口(实现)对象,然后把创建好的接口对象返回给客户端
  • 这样外部应用就只需要根据这对外交互的类来获取相应的接口对象,然后就可以操作接口定义的方法了。
  • 这样的类称为简单工厂,即Factory
效果
  1. 客户端可以通过Factory来获取需要的接口对象
  2. 客户端调用接口的方法来实现需要的功能
  3. 客户端不再关心具体实现了。
    在这里插入图片描述
/** 接口的定义,该接口可以通过简单工厂来创建*/   
public interface Api {  
    /**  * 示意,具体的功能方法的定义   */  
    public void operation(String s);  
} 
/*** 接口的具体实现对象A  . */  
public class ImplA implements Api{  
    public void operation(String s) {  
        //实现功能的代码,示意一下  
        System.out.println("ImplA s=="+s);  
    }  
} 
/**  * 工厂类,用来创造Api对象  */  
public class Factory {  
    /**      * 具体的创造Api对象的方法 
     * @param condition 示意,从外部传入的选择条件 ; @return 创造好的Api对象      */  
    public static Api createApi(int condition){  
        //根据某些条件去选择究竟创建哪一个具体的实现对象, 这些条件可以从外部传入,也可以从其它途径获取。如果只有一个实现,可省略条件。  
       Api api = null;  
        if(condition == 1){  
            api = new ImplA();  
        }else if(condition == 2){  
            api = new ImplB();   }  
        return api;   }  } 
/**  * 客户端,使用Api接口  */  
public class Client {  
    public static void main(String[] args) {  //通过简单工厂来获取接口对象  
        Api api = Factory.createApi(1);  
        api.operation("正在使用简单工厂");  
    }  } 

在这里插入图片描述

  • 简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。
  • 实现选择,需要选择的条件或者是选择的参数,其来源通常有几种:
  1. 来源于客户端,由Client来传入参数
  2. 来源于配置文件,从配置文件获取用于判断的值
  3. 来源于程序运行期的某个值,比如从缓存中获取某个运行期的值
  • 缺点:由于是从客户端在调用工厂的时候,传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露一定的内部实现细节

优缺点

  • 帮助封装
    简单工厂虽然很简单,但是非常友好地帮助我们实现了组件的封装,然后让组件外部能真正面向接口编程。
  • 解耦
    通过简单工厂,实现了客户端和具体实现类的解耦。
    客户端根本就不知道具体是由谁来实现,也不知道具体是如何实现的,客户端只是通过工厂获取它需要的接口对象。
  • 可能增加客户端的复杂度
    如果通过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所代表的具体功能和含义,这会增加客户端使用的难度,也部分暴露了内部实现,这种情况可以选用可配置的方式来实现。
  • 简单工厂的本质是:选择实现
  • 实现简单工厂的难点就在于 “如何选择”实现,前面讲到了几种传递参数的方法,那都是静态的参数,还可以实现成为动态的参数

比如:在运行期间,由工厂去读取某个内存的值,或者是去读取数据库中的值,然后根据这个值来选择具体的实现等等。

5. 工厂方法

  • 定义一个创建产品对象的工厂接口,让子类决定实例化哪一个类,将实际创建工作推迟到子类当中
  • 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构

在这里插入图片描述

/* 工厂方法所创建的对象的接口*/
abstract class Product {
    //可以定义Product的属性和方法
}
/** * 具体的Product对象 */
public class ConcreteProduct extends Product {
    //实现Product要求的方法
}
/*** 客户端使用Facotry对象的情况下,Factory的基本实现结构*/
abstract class Factory {  
	public abstract Product factoryMethod();  
} 

/**具体的创建器实现对象*/
class ConcreteFactory extends Factory {  
    public Product factoryMethod() {  
        return new ConcreteProduct();  
    }  } 
/*客户端程序*/
Factory factory;
factory = new ConcreteFactory(); 
//可通过配置文件实现
Product product;
product = factory.factoryMethod();
……

在实际使用时,具体工厂类在实现工厂方法时除了创建具体产品对象之外,还可以负责产品对象的初始化工作以及一些资源和环境配置工作,例如连接数据库、创建文件等。

在这里插入图片描述
在这里插入图片描述

//日志记录器接口:
//抽象产品  
 interface Logger {      
     public void writeLog();   }     
    //数据库日志记录器:具体产品   
class DatabaseLogger implements Logger {   
     public void writeLog() {  
          System.out.println("数据库日志记录");   
    }   }     
 //文件日志记录器:具体产品  
 class FileLogger implements Logger {
     public void writeLog() {  
          System.out.println("文件日志记录。");     
  }   }    
//日志记录器工厂接口:抽象工厂  
 interface LoggerFactory {      
      public Logger createLogger();   }      
      //数据库日志记录器工厂类:具体工厂  
 class DatabaseLoggerFactory implements LoggerFactory {      
       public Logger createLogger() {   
       //连接数据库,代码省略 ;创建数据库日志记录器对象               
       Logger logger = new DatabaseLogger(); 
        //初始化数据库日志记录器代码省略                  
        return logger;       }      }     
       //文件日志记录器工厂类:具体工厂   
class FileLoggerFactory implements LoggerFactory {       
        public Logger createLogger() {         
        //创建文件日志记录器对象                     
        Logger logger = new FileLogger();               
        //创建文件,代码省略             
       return logger;     
  }      }
class Client {  
    public static void main(String args[]) {  
        LoggerFactory factory;  
        Logger logger;
//可引入配置文件实现    
        factory = new FileLoggerFactory(); 
        logger = factory.createLogger();  
        logger.writeLog();  
    }  
}

在这里插入图片描述

简单工厂的优缺点

  • 优点:实现了责任分割
  • 利用判断逻辑,决定实例化哪一个产品类
  • 客户端可以免除直接创建产品类对象的责任,仅仅使用该产品
  • 缺点:没有完全做到“开-闭”
  • 一旦增加新的产品,需要修改工厂的代码
  • 但是客户代码不需要修改

简单工厂的问题

  • 所有具体产品对象的创建都放在一个类中,一旦增加新的产品,当然工厂类要被修改
  • 工厂方法:使用多态来应对
  • 提供一个抽象工厂的接口
  • 具体工厂分别负责创建具体产品对象
  • 增加新的产品只需要相应增加新的具体工厂类

Factory Method(工厂方法)模式

  • 意图
  • 定义一个用于创建对象的接口,让实现类(具体工厂类)决定实例化哪一个类
  • 使一个类的实例化延迟到实现类
  • 优点
  • 封装了创建具体对象的工作
  • 使得客户代码“针对接口编程”,保持对变化的**“关闭”(产品)**
  • 缺点
  • 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类
    (原本这个工厂生产A 但是想改为生产B了 需要把产品把A换成B)

在这里插入图片描述

6. 抽象工厂

在这里插入图片描述

  • 工厂方法模式或简单工厂关注的是单个产品对象或产品类的创建,比如创建CPU的工厂方法,它就只关心如何创建CPU的对象,而创建主板的工厂方法,就只关心如何创建主板对象。
  • 如何创建一系列的产品类对象,而且这一系列类对象是构建新的产品对象所需要的组成部分,也就是这一系列被创建的对象相互之间是有约束的

在这里插入图片描述

/** * 抽象工厂的接口,声明创建抽象产品对象的操作 */
public interface Factory {    
    /**     * 示例方法,创建抽象产品A的对象     
    @return 抽象产品A的对象     */    
       public ProductA createProductA();    
       /**     * 示例方法,创建抽象产品B的对象     
       @return 抽象产品B的对象     */    
       public ProductB createProductB();
}
/**  * 抽象产品A的接口 */  
public interface ProductA {  
    //定义抽象产品A相关的操作  
} 
/** 
/ * 产品A的具体实现 */  
public class ProductA1 implements ProductA {  
    //实现产品A的接口中定义的操作  
} 
public class ProductA2 implements ProductA {  
    //实现产品A的接口中定义的操作  
} 
//ProductB系列是类似的代码
/**  * 具体的工厂实现对象,实现创建具体的产品对象的操作  */  
public class Factory1 implements Factory {  
    public ProductA   createProductA() {  
       return new ProductA1();  
    }  
    public ProductB    createProductB() {  
       return new ProductB1();  
    }  
}  
/**  * 具体的工厂实现对象,实现创建具体的产品对象的操作  */  
public class Factory2 implements Factory {  
    public ProductA    createProductA() {  
       return new ProductA2();  
    }  
    public ProductB    createProductB() {  
       return new ProductB2();  
    }  
}  
public class Client {  
    public static void main(String[] args) {  
       //创建抽象工厂对象  
       Factory af = new Factory1();  
       //通过抽象工厂来获取一系列的对象,如产品A和产品B  
       af.createProductA();  
       af.createProductB();  
    }  
}  

抽象工厂模式的本质:选择产品簇的实现。(重要)

  • 工厂方法是选择单个产品的实现,虽然一个类里面可以有多个工厂方法,但是这些方法之间一般是没有联系的,即使看起来像有联系。
  • 抽象工厂着重的就是为一个产品簇选择实现,定义在抽象工厂里面的方法通常是有联系的,它们**都是产品的某一部分或者是相互依赖的。**如果抽象工厂里面只定义一个方法,直接创建产品,那么就退化成为工厂方法了。

何时选用抽象工厂模式

建议在如下情况中,选用抽象工厂模式:

  • 如果希望一个系统独立于它的产品的创建,组合和表示的时候,即,希望一个系统只是知道产品的接口,而不关心实现的时候。
  • 如果一个系统要由多个产品系列中的一个来配置的时候,即,可以动态地切换产品簇的时候。
  • 如果要强调一系列相关产品的接口,以便联合使用它们的时候。

抽象工厂与工厂方法

  • 工厂方法模式一般是针对单独的产品对象的创建,而抽象工厂模式注重产品簇对象的创建,这是它们的区别。
  • 如果把抽象工厂创建的产品簇简化,这个产品簇就只有一个产品,那么这个时候的抽象工厂跟工厂方法是差不多的,也就是抽象工厂可以退化成工厂方法,而工厂方法又可以退化成简单工厂,这是它们的联系。
  • 抽象工厂的需求:创建一个产品家族
    -负责在抽象工厂中创建产品的方法,通常是以“工厂方法”来实现的

7. 设计原则:依赖倒置原则DIP

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  • 抽象不应该依赖细节;细节应该依赖抽象。
  • 抽象(抽象类或接口)不应该依赖于细节(具体实现类)
  • 细节(具体实现类)应该依赖抽象
  • 抽象:即抽象类或接口,两者是不能够实例化的。
  • 细节:即具体的实现类,实现接口或者继承抽象类所产生的类,两者可以通过关键字new直接被实例化。
  • 而依赖倒置原则的本质就是通过抽象(抽象类或接口)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。
  • 这个原则也是OO设计原则中最难以实现的了,如果没有实现这个原则,那么也就意味着开闭原则(对扩展开放,对修改关闭)也无法实现。

在这里插入图片描述

DIP常见应用

  1. AGP插槽。主板和显卡之间关系的抽象。主板和显卡通常是使用AGP插槽来连接的,这样,只要接口适配,不管是主板还是显卡更换,都不是问题。
  2. 驾照。司机和汽车之间关系的抽象。有驾照的司机可以驾驶各种品牌汽车。
  3. 电源插座。
  • 设计模式中最能体现DIP原则的是抽象工厂模式。在抽象工厂模式中,工厂和产品都可以是抽象的,如果客户要使用的话,只要关注于工厂和产品的接口即可,不必关注于工厂和产品的具体实现。

疑惑:为什么是抽象工厂???

  • 依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合
  • 采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。

在这里插入图片描述

遵循倒置依赖原则的指导方针

  • 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备。
  • 变量不可以持有具体类的引用
  • 如果使用new,就会持有具体类的引用
  • 可以改用工厂来避开这样的做法
  • 不要让类派生自具体类
  • 如果派生自具体类,就会依赖具体类
  • 请派生自一个抽象(接口或抽象类)
  • 不是随时都要遵循这个原则
  • 如直接实例化字符串对象

依赖倒置有三种方式来实现

  1. 通过构造函数传递依赖对象
    比如在构造函数中的需要传递的参数是抽象类或接口的方式实现。
  2. 通过setter方法传递依赖对象
    即在setXXX方法中的参数为抽象类或接口,来实现传递依赖对象
  3. 接口声明实现依赖对象
    即在函数声明中参数为抽象类或接口,来实现传递依赖对象

通过构造函数传递依赖对象

在类中通过构造函数声明依赖对象,按照依赖注入的说法这种方式叫做构造函数注入

在这里插入图片描述

通过setter方法传递依赖对象

在抽象中设置setter方法声明依赖关系,依照依赖注入的说法就是setter依赖注入
在这里插入图片描述

接口声明实现依赖对象

在这里插入图片描述

理解“倒置”

  • “正置”:依赖正置就是类间的依赖是实实在在地实现类间的依赖,也就是面向实现编程,这也是正常人的思维方式,我要开奔驰车就依赖奔驰车,我要使用笔记本电脑就直接依赖笔记本电脑
  • 而编写程序需要的是对现实世界的事物进行抽象,抽象的结果就是有了抽象类和接口,然后我们根据系统设计的需要产生了抽象间的依赖,代替了人们传统思维中的事物间的依赖,“倒置”就是从这里产生的。
    在这里插入图片描述
    问题:
  • 司机类和奔驰车类之间是一个紧耦合的关系,其导致的结果就是系统的可维护性大大降低。
  • 代码可读性降低,两个相似的类需要阅读两个文件。
  • 系统稳定性降低,这里只是增加了一个车类就需要修改司机类,这不是稳定性,这是易变性。被依赖者的变更让依赖者来承担修改的成本。

设计原则:针对接口编程

在这里插入图片描述
“针对接口编程”的一些建议

  • 变量、参数、返回值等应声明为抽象类
  • 不要继承非抽象类
  • 不要重载父类的非抽象方法
    当然这些只是建议
    实际情况要权衡利弊

设计模式-Singleton单件/单例模式

  • 单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。
  • 如何实例化一个唯一的对象
  • 这样做的用途
  • 有些对象我们只需要一个:
    注册表 日志 线性池 缓存
    在这里插入图片描述
    单例模式的要点:
  1. 某个类只能有一个实例;
  2. 它必须自行创建这个实例;
  3. 它必须自行向整个系统提供这个实例。

经典单件模式实现

在这里插入图片描述
这个经典模式多个线程处理时不能获得同一个实例

处理多线程

在这里插入图片描述
多了一个 synchronized

急切(饿汉式)创建实例

在这里插入图片描述

双重检查加锁

在这里插入图片描述

关于双重加锁
  • volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确地处理该变量。
  • 注意:在Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问题,会导致双重检查加锁的失败,因此双重检查加锁的机制只能用在Java5及以上的版本。
  • 由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高,因此一般建议,没有特别的需要,不要使用

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值