设计模式-单例和工厂详解
1 具体内容
1.1 设计模式概念:
模式是一套被反复使用、多数人知晓的、经过分类编写的、成功代码设计经验的总结;它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
1.2 设计模式的作用:
1,可以提高程序员的思维能力、编程能力和设计能力。
2,使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
3,使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
1.3 设计模式类别:
分为三大类:
创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
1.4 设计模式遵循的原则有7个:
1、开闭原则(Open Close Principle)
对扩展开放,对修改关闭。
问题:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
2、里氏代换原则(Liskov Substitution Principle)
只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。 通俗简单的说:子类可以扩展父类的功能,但不能改变父类原有的功能。
S o1 = new S();
T o2 = new T();
P 功能模块 o2->o1 S extends T
问题 :有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。【有时候我们可以采用final的手段强制来遵循】
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象
问题 :类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
4、接口隔离原则(Interface Segregation Principle)
使用多个隔离的接口来降低耦合度。
问题:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
DeptService 5个接口 cr(2个)ud
UserService 5个接口 cr(2个)ud
OrderService 5个接口 cr(2个)ud
GlobalService 15个接口 queryDeptAll queryUserAll queryOrderAll… addDept addUser…
5、迪米特法则(最少知道原则)(Demeter Principle)sp
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
问题:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方法:尽量降低类与类之间的耦合。
不要和陌生人说话 在该原则下诞生 门店模式。。。美团 早餐,午餐 ,晚餐
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
问题:B类如果继承了A类,A类可继承方法m的实现细节暴露给B类,如果A类发生方法m改变,那么B的实现也不得不发生改变。
解决方法:使用合成或者聚合,不要使用继承
B b m1 调用类A中的非私有方法m2方法
第一种: B extends A
第二种: class B{
private A a =new A();
}
B b=new B() A a = b.getA() a.m2()
聚合:B的对象销毁了,对A的对象没有任何影响
合成:B的对象销毁了, A的对象也不存在
7、单一职责原则(Single responsibility principle)
一个类只负责一个功能领域的响应职责。 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。另外,多个职责耦合在一起,会影响复用性。
问题:比如一个类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
解决方法:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。
1.5 常用设计模式:
1 工厂模式(Factory Method)
总概念:
工厂模式属于创建型设计模式,它提供了一种创建对象的最佳方式。隐藏复杂的逻辑处理过程, 只关心执行结果。直接用new可以完成的不需要用工厂模式.需要生成复杂对象的地方使用
1)简单工厂模式 又可以称为静态工厂模式
概念:在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类或者接口。
优点:功能强大,对象创建和使用分离,程序员可以只关心对象使用,不用关心对象如何创建
缺点:耦合度高(所有产品都在工厂创建,一旦异常其他产品也受影响),扩展性不强(每次添加一个产品,工厂类都要变化),违背开闭原则
1,定义接口
package com.aaa.dp.factory.simple;
/**
* @ fileName:SoftwareTechnology
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 10:40
* @ version:1.0.0
*/
public interface SoftwareTechnology {
/**
* 学习软件技术
*/
void studyST();
}
2,编写实现类
package com.aaa.dp.factory.simple;
/**
* @ fileName:JavaDevTechnology
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 10:41
* @ version:1.0.0
*/
public class JavaDevTechnology implements SoftwareTechnology {
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习java技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}
package com.aaa.dp.factory.simple;
/**
* @ fileName:PythonDevTechnology
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 10:45
* @ version:1.0.0
*/
public class PythonDevTechnology implements SoftwareTechnology{
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习Python技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}
package com.aaa.dp.factory.simple;
/**
* @ fileName:UIDevTechnology
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 10:44
* @ version:1.0.0
*/
public class UIDevTechnology implements SoftwareTechnology {
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习UI技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}
3,创建工厂
package com.aaa.dp.factory.simple;
/**
* @ fileName:AAAFactory
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 10:46
* @ version:1.0.0
*/
public class AAAFactory {
/**
* 工厂方法
* @param type
* @return
*/
public SoftwareTechnology teachST(int type){
if(type==1){
return new JavaDevTechnology();
}else if(type==2){
return new UIDevTechnology();
}else if(type==3){
return new PythonDevTechnology();
}else {
return null;
}
}
}
4,测试
package com.aaa.dp.factory.simple;
/**
* @ fileName:Test
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 10:49
* @ version:1.0.0
*/
public class Test {
public static void main(String[] args) {
AAAFactory aaaFactory = new AAAFactory();
SoftwareTechnology softwareTechnology = aaaFactory.teachST(4);
if(softwareTechnology!=null){
softwareTechnology.studyST();
}else {
System.out.println("没有这门技术对应的编号!");
}
}
}
2)工厂(方法)模式:
概念:
工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
概念解释:
工厂本身不再创建产品,而是规定了工厂规范,即工厂接口,而将产品创建都交给子工厂创建。
优点: 遵循了开闭原则(不需要修改工厂类,就可以增加产品)
解耦,职责单一(每个工厂只负责创建对应的产品)
缺点: 增加系统复杂度(每新加一个产品需要新加一个工厂)
1,定义工厂接口
package com.aaa.dp.factory.method;
import com.aaa.dp.factory.simple.SoftwareTechnology;
/**
* @ fileName:AAAStanderdFactory
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:02
* @ version:1.0.0
*/
public interface AAAStanderdFactory {
/**
* 规定了开分销的所有规范
*/
SoftwareTechnology teachST();
}
2,编写工厂实现类
package com.aaa.dp.factory.method;
import com.aaa.dp.factory.simple.SoftwareTechnology;
/**
* @ fileName:BeiJingAAAFactory
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:07
* @ version:1.0.0
*/
public class BeiJingAAAFactory implements AAAStanderdFactory {
@Override
public SoftwareTechnology teachST() {
return new PhpDevTechnology();
}
}
package com.aaa.dp.factory.method;
import com.aaa.dp.factory.simple.SoftwareTechnology;
/**
* @ fileName:WuHanAAAFactory
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:04
* @ version:1.0.0
*/
public class WuHanAAAFactory implements AAAStanderdFactory {
@Override
public SoftwareTechnology teachST() {
return new BigDataDevTechnology();
}
}
3,测试
package com.aaa.dp.factory.method;
import com.aaa.dp.factory.simple.SoftwareTechnology;
/**
* @ fileName:Test
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:07
* @ version:1.0.0
*/
public class Test {
public static void main(String[] args) {
AAAStanderdFactory aaaStanderdFactory =new BeiJingAAAFactory();
SoftwareTechnology softwareTechnology = aaaStanderdFactory.teachST();
softwareTechnology.studyST();
}
}
3)抽象工厂
概念:抽象工厂是工厂方法的升级版,为相关或者相互依赖的对象提供一个接口,而且无须指定他们的具体实现类。
概念解释:抽象工厂模式相对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:难以支持新种类的产品。因为抽象工厂接口确定了可以被创建的产品集合,所以难以扩展抽象工厂以生产新种类的产品。
1,定义抽象接口(包括简单和工厂方法)
package com.aaa.dp.factory.abstact1;
import com.aaa.dp.factory.simple.SoftwareTechnology;
/**
* @ fileName:AAAAstractFactory
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:18
* @ version:1.0.0
*/
public interface AAAAstractFactory {
/**
* 教授软件技术
* @return
*/
SoftwareTechnology teachST();
/**
* 教授挖掘机技术
* @return
*/
DiggerTechnology teachDT();
}
2,编写实现工厂
package com.aaa.dp.factory.abstact1;
import com.aaa.dp.factory.method.AAAStanderdFactory;
import com.aaa.dp.factory.simple.SoftwareTechnology;
/**
* @ fileName:ShenzhenAAAFactory
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:22
* @ version:1.0.0
*/
public class ShenzhenAAAFactory implements AAAAstractFactory {
@Override
public SoftwareTechnology teachST() {
return new CDevThechnology();
}
@Override
public DiggerTechnology teachDT() {
return new BigDiggerTechnology();
}
}
3,测试两种方式
package com.aaa.dp.factory.abstact1;
import com.aaa.dp.factory.simple.SoftwareTechnology;
/**
* @ fileName:Test
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:23
* @ version:1.0.0
*/
public class Test {
public static void main(String[] args) {
AAAAstractFactory aaaAstractFactory =new ShenzhenAAAFactory();
DiggerTechnology diggerTechnology = aaaAstractFactory.teachDT();
diggerTechnology.studyDT();
SoftwareTechnology softwareTechnology = aaaAstractFactory.teachST();
softwareTechnology.studyST();
}
}
总结:
无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦,让类的创建和使用过程实现松耦合。
2 单例(态)模式(Singleton)
1)概念:
一种常用的软件设计模式。所谓单例,就是让一个类在项目运行中只存在一个对象,即使用到这个类的地方很多,也只存在一个对象。
2) 好处:
1,节省内存
2,有些情况下不用单例模式可能会引起代码逻辑错误(例如:网站访问量统计功能ServletContext (servlet) Application(jsp)) application.setAttrbute("count",100);
3) 单例模式要点
- 是单例模式的类只提供私有的构造函数
- 是类定义中含有一个该类的静态私有对象;
- 是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
实现方式(7种):
懒汉(slacker):该单例类非常懒,只有在自身需要的时候才会行动,从来不知道及早做好准备。特点是运行时获得对象的速度比较慢,但加载类的时候比较快。整个应用的生命周期只有一部分时间在占用资源。
1)懒汉线程不安全:
因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
package com.aaa.dp.singleton;
/**
* @ fileName:SlackerThreadUnsafe
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:38
* @ version:1.0.0
*/
public class SlackerThreadUnsafe {
//1,私有的构造方法 防止在其他地方创建对象
private SlackerThreadUnsafe(){};
//2,静态私有的类实例 static 伴随着类的加载而执行,并且只执行一次 私有 防止该属性在其他类中被访问
private static SlackerThreadUnsafe instance;
//3, 公共静态的方法,返回该类的实例
public static SlackerThreadUnsafe getInstance(){
if(instance==null){
instance = new SlackerThreadUnsafe();
}
return instance;
}
}
2)懒汉线程安全
必须加锁 synchronized 才能保证单例,但加锁多少会影响效率
package com.aaa.dp.singleton;
/**
* @ fileName:SlackerThreadUnsafe
* @ description:
* @ author:zhz
* @ createTime:2021/12/2 11:38
* @ version:1.0.0
*/
public class SlackerThreadSafe {
//1,私有的构造方法 防止在其他地方创建对象
private SlackerThreadSafe(){};
//2,静态私有的类实例 static 伴随着类的加载而执行,并且只执行一次 私有 防止该属性在其他类中被访问
private static SlackerThreadSafe instance;
//3, 公共静态的方法,返回该类的实例
public static synchronized SlackerThreadSafe getInstance(){
if(instance==null){
instance = new SlackerThreadSafe();
}
return instance;
}
}
3)懒汉线程安全双重加锁
采用双锁机制,安全且在多线程情况下能保持高性能。
package com.aaa.singleton.demo;
/**
* @ fileName:Slacker
* @ description:懒汉模式(线程安全双重加锁)
* @ author:zhz
* @ createTime:2020/3/17 9:36
*/
public class SlackerThreadSafeDoubleLock {
private SlackerThreadSafeDoubleLock(){};
//volatile 多线程可见,当一个线程修改后,其他线程都可以见
private volatile static SlackerThreadSafeDoubleLock slacker;
//volatile关键字作用为禁止指令重排,保证返回
// (1)在堆上开辟空间;(2)属性初始化;(3)引用指向对象
//假设以上三个内容为三条单独指令,因指令重排可能会导致执行顺序为1->3->2(正常为1->2->3),当单例模式中存在普通变量需要在构造方法中进行初始化操作时,单线程情况下,顺序重排没有影响;但在多线程情况下,假如线程1执行singleton=new Singleton()语句时先1再3,由于系统调度线程2的原因没来得及执行步骤2,但此时已有引用指向对象也就是singleton!=null,故线程2在第一次检查时不满足条件直接返回singleton,此时singleton为null(即str值为null)
public SlackerThreadSafeDoubleLock getInstance(){
//多线程进入
if(slacker==null){
//只有一线程执行
synchronized (SlackerThreadSafeDoubleLock.class) {
//防止一个线程执行完,其他线程拿到锁,再次执行对象创建
if(slacker==null) {
slacker = new SlackerThreadSafeDoubleLock();
}
}
}
return slacker;
}
}
线程安全单例模式:上述的实现方式很容易会想到存在一个严重的缺陷,就是“非线程安全”。最简单的方式就是通过synchronized关键字来实现线程同步,但同步锁是比较耗费资源的,如果在程序中频繁地获取对象,这样的话效率就大大地降低了。所以说,在单例中添加同步锁的方法比较适用于对对象获取不是很频繁地情况。
饿汉(starving):该单例类非常饿,迫切需要吃东西,所以它在类加载的时候就立即创建对象。特点是加载类的时候比较慢,但运行时获得对象的速度比较快。从加载到应用结束会一直占用资源。
4)饿汉线程安全
没有加锁,执行效率会提高。类加载时就初始化,浪费内存。
package com.aaa.singleton.demo;
/**
* @ fileName:Starving
* @ description:饿汉(线程安全)
* @ author:zhz
* @ createTime:2020/3/17 7:20
*/
public class Starving {
private Starving(){};
private static final Starving STARVING = new Starving();
public Starving getInstance(){
return STARVING;
}
}
5)饿汉静态线程安全
没有加锁,执行效率会提高。类加载时就初始化,浪费内存。当单例对象产生过程比较复杂时,使用这种方式。
package com.aaa.singleton.demo;
/**
* @ fileName:Starving
* @ description:饿汉(线程安全)
* @ author:zhz
* @ createTime:2020/3/17 7:20
*/
public class StarvingStaticCodeBlock {
private StarvingStaticCodeBlock(){};
private static final StarvingStaticCodeBlock STARVING;
static {
STARVING = new StarvingStaticCodeBlock() ;
}
public StarvingStaticCodeBlock getInstance(){
return STARVING;
}
}
6) 枚举单例模式
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
package com.aaa.singleton.demo;
/**
* @ fileName:EnumerationSingleton
* @ description:
* @ author:zhz
* @ createTime:2020/3/17 8:03
*/
public enum EnumerationSingleton {
ENUMERATION_SINGLETON;
public void doAnything(){
System.out.println("doAnything");
}
}
7) 静态内部类
package com.aaa.singleton.demo;
/**
* @ fileName:StaticInternal
* @ description:静态内部类 (实例消耗资源时,实现延迟加载)外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
* @ author:zhz
* @ createTime:2020/3/17 7:47
*/
public class StaticInternal {
private StaticInternal(){};
private static class StaticInternalClass{
private static final StaticInternal STATIC_INTERNAL =new StaticInternal();
}
public static final StaticInternal getInstance(){
return StaticInternalClass.STATIC_INTERNAL;
}
}
(实例消耗资源时,实现延迟加载)
更多推荐
所有评论(0)