设计模式

工厂模式

  工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式,工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。

https://www.zhihu.com/question/24843188?sort=created

设计模式的一个重要原则就是:别改代码,只需要添代码,以前所有的老代码,都是有价值的,需要尽力保留new一个对象时,new的过程是宝贵的如何创建老对象的知识点(有的new很复杂,包括了很多参数),如果这个代码被修改了,那么保留的老对象也不知道怎么使用了,整个体系残缺了所以要想办法保留老对象的new过程,把这个new过程保存分布到一系列工厂类里,就是所谓的工厂模式,一般有三种方式来封装简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。这个缺点是每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个switch case,但仍然违背了“不改代码”的原则)

  工厂方法:每种产品由一种工厂来创建,一个工厂保存一个new基本完美,完全遵循“不改代码”的原则抽象工厂:仅仅是工厂方法的复杂化,保存了多个new大工程才用的上

工厂模式中有: 工厂方法(FactoryMethod) 抽象工厂(AbstractFactory).

工厂方法:定义一个用于创建对象的接口让子类来决定创建哪一个具体类。

 参与角色:  抽象产品角色具体产品角色  抽象工厂角色  具体产品工厂角色。该模式继承了简单工厂模式的优点又克服了它的缺点但它本身也有缺点就是每增加一个产品类的时候都需要增加一个具体产品工厂类增加了额外的开发量 

抽象工厂模式:该模式和与工厂方法模式很相似,也是有个参与角色,抽象产品角色,具体产品角色,抽象工厂角色,具体工厂角色,不同的是抽象工厂类里面有多个虚工厂方法  它在我们系统实际设计当中考虑的是数据库的迁移,到时候再更改数据访问层会很麻烦,因为每种数据库的语法是不一样的,我们不能保证在以后肯定不会这么做,所以要在起初设计的时候就要考虑到这点

这两个模式区别在于需要创建对象的复杂程度上。如果我们创建对象的方法变得复杂了,如上面工厂方法中是创建一个对象Sample,如果我们还有新的产品接口Sample2.

这里假设:Sample有两个实体类SampleASampleB,而Sample2也有两个实体类Sample2ASample2B

那么,我们就将上例中Factory变成抽象类,将共同部分封装在抽象类中,不同部分使用子类实现,下面就是将上例中的Factory拓展成抽象工厂:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

简单工厂:

public class Factory{

    public static ISample creator(int which){
        if (which==1)
            return new SampleA();
        else if (which==2)
            return new SampleB();
    }
}

public abstract class Factory{

    public abstract Sample creator();

    public abstract Sample2 creator(String name);

}

工厂方法:

public class SimpleFactory extends Factory{

    public Sample creator(){

        .........

        return new SampleA

    }

    public Sample2 creator(String name){

        .........

        return new Sample2A

    }

}

 

public class BombFactory extends Factory{

    public Sample creator(){

        ......

        return new SampleB

    }

    public Sample2 creator(String name){

        ......

        return new Sample2B

    }

}

从上面看到两个工厂各自生产出一套SampleSample2,为什么不可以使用两个工厂方法来分别生产SampleSample2?

抽象工厂还有另外一个关键要点,是因为 SimpleFactory内,生产Sample和生产Sample2的方法之间有一定联系,所以才要将这两个方法捆绑在一个类中,这个工厂类有其本身特征,也许制造过程是统一的,比如:制造工艺比较简单,所以名称叫SimpleFactory

在实际应用中,工厂方法用得比较多一些,而且是和动态类装入器组合在一起应用。

简单工厂模式:他的最大优点就是在于工厂类包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖,当算法比较稳定,一般不会对它进行新增,或删除等就适合用词模式;否则就会违背了开放-封闭原则

工厂方法模式:它定义了用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。

抽象方法模式:当产品有不同的系列,而不同的系列又有不同的创建方式,此时就适合用此模式

优点:克服了简单工厂违背开放-封闭原则的缺点,又保留了封装对象创建过程的优点,降低客户端和工厂的耦合性,所以说“工厂模式”是“简单工厂模式”的进一步抽象和推广。
缺点:每增加一个产品,相应的也要增加一个子工厂,加大了额外的开发量。

单例模式

是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义

显然单例模式的要点有三个一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

优缺点

优点

一、实例控制

单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

二、灵活性

因为类控制了实例化过程,所以类可以灵活更改实例化过程。

缺点

一、开销

虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

二、可能的开发混淆

使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

三、对象生存期

不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。


单例模式可以分为懒汉式和饿汉式

懒汉式单例模式:在类加载时不初始化。

饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。

第一种(懒汉,线程不安全):

publicclass SingletonDemo1 {

   privatestatic SingletonDemo1 instance;

   private SingletonDemo1(){}

   publicstatic SingletonDemo1 getInstance(){

       if (instance == null) {

            instance = new SingletonDemo1();

       }

       return instance;

   }

}

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。 

    第二种(懒汉,线程安全):

publicclass SingletonDemo2 {

   privatestatic SingletonDemo2 instance;

   privateSingletonDemo2(){}

   publicstaticsynchronized SingletonDemo2 getInstance(){

       if (instance == null) {

            instance = new SingletonDemo2();

       }

       return  instance;

   }

}

这种写法在getInstance()方法中加入了synchronized锁。能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是效率很低(因为锁),并且大多数情况下不需要同步。

    第三种(饿汉):

publicclass SingletonDemo3 {

   privatestatic SingletonDemo3 instance = new SingletonDemo3();

   privateSingletonDemo3(){}

   publicstatic SingletonDemo3 getInstance(){

       return instance;

   }

}

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,这时候初始化instance显然没有达到lazy loading的效果。

 第四种(饿汉,变种):

publicclass SingletonDemo4 {

   privatestatic SingletonDemo4 instance = null;

   static{

       instance = newSingletonDemo4();

   }

   privateSingletonDemo4(){}

    publicstaticSingletonDemo4 getInstance(){

       return instance;

   }

}

表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance

    第五种(静态内部类):

publicclass SingletonDemo5 {

   privatestaticclass SingletonHolder{

       privatestaticfinal SingletonDemo5 instance = new SingletonDemo5();

   }

   privateSingletonDemo5(){}

   publicstaticfinal SingletonDemo5 getInsatance(){

       return SingletonHolder.instance;

   }

}

  这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方法就显得更合理。

第六种(枚举):

publicenum SingletonDemo6 {

   instance;

   publicvoid whateverMethod(){

   }

}

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

第七种(双重校验锁):

publicclass SingletonDemo7 {

   privatevolatilestatic SingletonDemo7 singletonDemo7;

   privateSingletonDemo7(){}

   publicstatic SingletonDemo7 getSingletonDemo7(){

       if (singletonDemo7 == null) {

            synchronized (SingletonDemo7.class) {

                if (singletonDemo7 == null) {

                    singletonDemo7 = new SingletonDemo7();

                }

            }

       }

       returnsingletonDemo7;

   }

}

适用场景 
   
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如: 
    1.
需要频繁实例化然后销毁的对象。 
    2.
创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 
    3.
有状态的工具类对象。 
    4.
频繁访问数据库或文件的对象。 
以下都是单例模式的经典使用场景: 
    1.
资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。 
    2.
控制资源的情况下,方便资源之间的互相通信。如线程池等。 
应用场景举例: 
    1.
外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件 
    2. Windows
TaskManager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗?不信你自己试试看哦
    3. windows
RecycleBin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。 
    4.
网站的计数器,一般也是采用单例模式实现,否则难以同步。 
    5.
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 
    6. Web
应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 
    7.
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。 
    8.
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。 
    9.
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。 
    10. HttpApplication
也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例

装饰模式

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

特点

1装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。

2装饰对象包含一个真实对象的引用(reference

3装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。

4装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

适用性

以下情况使用Decorator模式

1. 需要扩展一个类的功能,或给一个类添加附加职责。

2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。

3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。

4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

优点

1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。

2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点

1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。

2. 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。

3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现半透明的装饰者模式。在实际项目中要做出最佳选择。

设计原则

1. 多用组合,少用继承。

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。

2. 类应设计的对扩展开放,对修改关闭。

     装饰器(Decorator)和被装饰的对象(ConcreteComponent)拥有统一的接口,这个统一的接口正是被装饰对象需要扩展功能的地方,因此它们需要一个共同的接口(AbstractComponent)。另外为了完成装饰的目的,装饰器需要包含被装饰的对象,装饰器不直接包含被装饰对象,而是包含它们统一接口的引用,这样通过多态机制可以实现多层装饰。注意装饰器对抽象接口的关系是一对一的,这和组合模式很像,但是一对一的关系决定了装饰器一次只能装饰一个对象,这种关系正是我们想要的。装饰器为被装饰对象添加功能通过调用被装饰对象的统一接口实现,如果装饰器需要复杂的扩展,我们通过继承装饰器实现具体的装饰器(ConcreteDecorator),具体的装饰器可以扩展数据(addedState),也可以扩展接口功能(addedBehavior)。

 

代理模式

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

优点

(1).职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

(3).高扩展性

模式结构

一个是真正的你要访问的对象(目标类),一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。代理模式分为静态代理、动态代理。

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

#include <iostream>
using namespace std;
class RealImage {
 int m_id;
 public:
 RealImage(int i) {
   m_id = i;
    cout << "   $$ ctor: " << m_id << '\n';
 }
 ~RealImage() {
   cout << "   dtor: " << m_id << '\n';
 }
 void draw() {
   cout << "   drawing image " << m_id << '\n';
 }
};
// 1. Design an "extra level of indirection" wrapper class
class Image
{
// 2. The wrapper class holds a pointer to the real class
 RealImage *m_the_real_thing;
 int m_id;
 static int s_next;
 public:
 Image() {
   m_id = s_next++;
    // 3. Initialized to null
   m_the_real_thing = 0;
 }
 ~Image() {
   delete m_the_real_thing;
 }
 void draw() {
   // 4. When a request comes in, the real object is
    //    created "on first use"
   if (!m_the_real_thing)
   m_the_real_thing = new RealImage(m_id);
   // 5. The request is always delegated
   m_the_real_thing->draw();
 }
};
 int Image::s_next = 1;
 int main() {
   Image images[5];
    for (int i; true;) {
   cout << "Exit[0], Image[1-5]: ";
    cin >> i;
   if (i == 0)
   break;
   images[i - 1].draw();
 }
 system("Pause");
};


代理模式和装饰者模式的区别:代理模式中,代理类对被代理的对象有控制权,决定其执行或者不执行。而装饰模式中,装饰类对代理对象没有控制权,只能为其增加一层装饰,以加强被装饰对象的功能,仅此而已。 

观察者模式

观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

使用观察者模式的场景和优缺点

使用场景

·        关联行为场景,需要注意的是,关联行为是可拆分的,而不是组合关系。

·        事件多级触发场景。

·        跨系统的消息交换场景,如消息队列、事件总线的处理机制。

优点

解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。

缺点

在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式

介绍

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、京东上某个商品暂时没货,提示用户关注后到货通知,这个暂时无货的商品是被观察者,点击关注这个商品的用户就是观察者。 2、老师针对成绩在60分以下的同学定期发送最新的考题分析邮件,每轮考试下来都会有不及格的同学,由不及格变为及格的同学自动从邮件列表里移除,新的不及格的同学会被加进邮件列表里。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

注意事项: 1JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

下面这个实例是系统中常会遇到的:

场景描述:
*
系统有一个模块:工单管理,它有三个子模块,工单创建,工单激活,工单处理
*
系统有两个角色:管理员、安装工
*
管理员可以操作所有的子模块,安装工只能操作工单处理
*
现在需要添加删除子模块的时候自动通知到对应的角色,使其添加删除对应的模块权限

 




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值