设计模式学习


 

 

本文摘自:http://zz563143188.iteye.com/blog/1847029

一,设计模式的分类

 总体来说,设计模式分为三大类:

①创建型模式(5种) :单例模式,工厂方法模式,抽象工厂模式,原型模式,建造者模式。

②结构型模式(7种) :适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。

③行为型模式(11种):策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式。

其实还有两类:并发型模式和线程池模式。用一个图片来整体描述下:

二,设计模式的原则

1、开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

5、迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

原则是尽量使用合成/聚合的方式,而不是使用继承。

 

其他详细的。。略。。

 

本文摘自:http://zz563143188.iteye.com/blog/1847029

 

===========================================================================

追加一些笔记

设计模式学习
 ①代理模式:代理模式的一种常见的实现方案是,定义一个接口或抽象类,并派生出目标子类,
和代理子类。
  重点:我们要操作的是目标子类里的方法,而很多时候,我们需要为目标子类中的方法增加额外的处理,
如果增加日志功能、条件判断等,这时候,就很有必要用到代理类。
  代码关键:用代理类同样实现公共接口;并且在构造方法中注入原来接口的实例。【这样我们在构造代理类的时候,可以把原来目标类作为参数传递进去。】
            并且在实现接口方法的时候,调用原来目标类的实现方法,并加上自己需要多出来的处理代码。
    --------------------
可以看出静态代理类有一个很不爽的缺点:当如果接口加一个方法,所有的实现类和代理类里都需要做个实现。这就增加了代码的复杂度。动态代理就可以避免这个缺点。
   动态代理与普通的代理相比较,最大的好处是接口中声明的所有方法都被转移到一个集中的方法中处理(invoke),这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理

那样每一个方法进行中转。【动态代理似乎使用了反射机制】

而加入反射机制的代理模式,可实现一个公共的代理类,省去我们不少功夫。Java的java.lang.reflect包及其子包中提供了Class、Method、Annotation等有用的类。下面,写个方法代理的类MethodProxy

,实现动态地调用对象的方法。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 方法代理类
* @author rongxinhua
*
*/
public class MethodProxy {
 
 private Class clazz; //对象所属的类
 private Object target; //目标对象
 private Method method; //目标方法
 private Object[] params; //参数数组
 
 @SuppressWarnings("unchecked")
 public MethodProxy(Object target, String methodName, Object ... params) {
  rebindTarget(target, methodName, params); //设置目标对象与方法
 }
 
 /**
  * 重新设置目标对象与方法
  * @param target
  * @param methodName
  * @param params
  */
 public void rebindTarget(Object target, String methodName, Object ... params) {
  this.target = target;
  this.clazz = target.getClass();
  rebindMethod(methodName, params); //设置目标方法
 }
 
 /**
  * 重新设置目标方法
  * @param methodName
  * @param params
  */
 public void rebindMethod(String methodName, Object ...params) {
  this.params = params;
  int paramLength = params.length;
  Class[] paramTypes = new Class[paramLength];
  for(int i = 0 ; i < paramLength ; i ++ ) {
   paramTypes[i] = params[i].getClass();
  }
  try {
   this.method = clazz.getMethod(methodName, paramTypes);
  } catch (SecurityException e) {
   e.printStackTrace();
  } catch (NoSuchMethodException e) {
   e.printStackTrace();
  }
 }
 
 /**
  * 动态调用已绑定的方法
  */
 public void doMethod() {
  try {
   this.method.invoke(target, params);
  } catch (IllegalArgumentException e) {
   e.printStackTrace();
  } catch (IllegalAccessException e) {
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   e.printStackTrace();
  }
 }
}
这样就可以实现动态地调用某个对象的某个方法了,写个测试代码如下:

public class Manager {
 
 public void say() {
  System.out.println("Nobody say nothing");
 }
 
 public void love(String boy, String girl) {
  System.out.println(boy + " love " + girl);
 }
 
}
 我们通过代理类来调用Manager类中的say()和love()方法,测试代码如下:

  Manager man = new Manager(); //目标对象
  MethodProxy proxy = new MethodProxy(man, "say"); //方法代理对象
  proxy.doMethod(); //调用被代理的方法
  proxy.rebindMethod("love", "Tom", "Marry"); //重新绑定方法
  proxy.doMethod(); //调用被代理的方法
这样就实现了动态代理调用对象的方法,上面代码输出结果就不贴出来了。如果要设置前置通知和后置通知等功能,也很容易实现,只需在“proxy.doMethod()”代码处的前面和后面设置即行。


②回调 callback
   回调表示一段可执行逻辑的引用(或者指针),我们把该引用(或者指针)传递到另外一段逻辑(或者方法)里供这段逻辑适时调用。C语言里经常使用函数指针实现回调,C#语言里使用代理delegate

实现,而在java语言里面我们使用内部匿名类实现回调。

【泛型是指能够在运行时动态得到对象类型的一种能力,这样,我们就没有必要每次都写强制类型转换语句了。其实java是在编译时为生成的二进制代码加入强制类型转换的语句,并非真正在运行时得到

对象的类型。】
4.自定义泛型:
4-1.泛型定义在方法上:放之前定义
     public <T> void test1(T t){}
     public static <T> void test2(T t){}
     public <T> int test3(T t){ return 0; }
     public <T> List<T> test4(){ return null;  }
     public static <EE> void test5(EE e){}
4-2.泛型定义在类上:如果一个同时用到同一个泛型那么可以把泛型定义到类上 ,可以声明一个或多个
     public class StudentDao<T,E>{
     public void test(T t){}
     public List<T> test2(){ return null; } 

 

③单例模式,注意将它变成线程安全的。
 public class ThreadSafeSingleton{
   private static ThreadSafeSingleton threadSafeSingleton;

   private ThreadSafeSingleton(){
   }
   public static ThreadSafeSingleton getInstance(){
     if(threadSafeSingleton == null){
        threadSafeSingleton = new ThreadSafeSingleton();
     }
        return threadSafeSingleton;
  }
 }


  但实际上,加上synchronized会使得性能大不如前。但其实使用synchronized关键字对整个getInstance方法进行同步是没有必要的:我们只要保证实例化这个对象的那段逻辑被一个线程执行就可以了,

而返回引用的那段代码是没有必要同步的。修改后的代码:
   public class DoubleCheckSingleton{
       private volatile static DoubleCheckSingleton instance = null;

       private DoubleCheckSingleton(){}

       public static DoubleCheckSingleton getInstance(){
             if(instance ==null){
                   synchronized(DoubleCheckSingleton.class){  // synchronize creation block。synchronized关键字包含的一般是变量。
                         if(instance == null){              // double check if it is created
                             instance = new DoubleCheckSingleton();
                         }
                   }
             }           
         return instance;
      }
   }
  在getInstance方法里面,我们首先判断此实例是否已经被创建了,如果还没有创建,首先使用synchronized同步实例化代码块。在同步代码块里,我们还需要再次检查是否已经创建了此类的实例,这是

因为:如果没有第二次检查,这是有两个线程ThreadA 和Thread B同时进入该方法,它们都检测到instance为null,不管哪一个线程先占据同步锁创建实例对象,都不会阻止另外一个线程继续进入实例化

代码块重新创建实例对象,这样,同样会发生两个实例对象。所以,我们在同步的代码块里,进行第二次判断,判断该对象是否已被创建。  另外:属性instance是被volatile修饰的,因为Volatile具有

synchronized的可见性特点,也就是说线程能够自动发现volatile变量的最新值。这样,如果instance实例化成功,其他线程便能立即发现。
  上面的程序只能在java 5以上版本执行,因为之前的版本 java平台的内存模式容许 out-of-order writes。在instance没有完全实例化时,instance变量就可以指向此实例,导致其他线程直接使用了这

个未初始化完成的实例,结果可能引起系统崩溃。

  还有一种方法,称为:Initialization on demand holder模式。
   public class LazyLoadedSingleton{
         private LasyLoadedSingleton(){}
         private static class LazyHolder{
              private static final LazyLoadedSingleton singletonInstance = new LazyLoadedSingleton();
          }     
       public static LazyLoadedSingleton instance(){ return LazyHolder.sinletonInstance;}
   }
  JLS保证,上述过程不会发生并发问题,这样就实现了一个 既线程安全又支持延迟加载的单例模式。

  如果单例类实现了Serializable接口,这时我们得特别注意,因为我们知道在默认情况下,每次反序列化总会创建一个新的实例对象,这样一个系统会出现多个对象供使用。。。。 我们需要在

readResolve方法里面做文章,此方法在反序列化完成之前被执行,我们在此方法里替换掉反序列化出来的那个新的实例,让其指向内存中的那个单例对象即可,代码如下:
  public class SerialibleSingleton implements Serializable{
   private static final long serialVersion UID = -23452345234623464634L;  // 乱写的数字
   static SerialibleSingleton singleton = new SerialibleSingleton();
   private SerialibleSingleton(){};
   private Object readResolve(){return singelton;}
  }


 ④ 原型模式:prototype。 使用原型实例指定将要创建的对象类型,通过拷贝这个实例创建新的对象。
   浅拷贝和深拷贝的概念。浅拷贝只拷贝了对象的引用,而没有在内存中复制对象。 使用java.lang.Cloneable接口
   优点:方便实例化,只要复制对象,然后初始化对象,就可以得到你想要的对象,并不需要过多的编程。

⑤ 控制反转IoC
   我们经常把控制逻辑写在其他地方(例如Framework)而非客户化的代码里,我们就更专注于客户化的逻辑,也就是说,外部逻辑负责调用我们客户化的逻辑。
   控制反转 Inversion Of Control 调用者不再创建被调用者的实例,由spring框架实现(容器创建)所以称为控制反转。
   依赖注入: 容器创建好实例后再注入调用者称为依赖注入。【目前分为6类:Setter注入,Constructor注入,Annotation注入,Interface注入,Parameter注入和 其他形式的注入。】

当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例,。如果创建被调用者实例的工作不再由

调用者来完成,而是由外部容器完成,因此称为控制反转; 创建被调用者 实例的工作通常由外部容器来完成,然后注入调用者,因此也称为依赖注入

 ⑥装饰器模式
     装饰器模式可以让我们在运行时动态地增加对象的功能,而不影响客户程序的使用。
    OCP Open-Closed Principle 开放-封闭原则。软件实体(类,模块,功能等等)应当对外扩展开放,但是对修改关闭。

  代理模式和装饰器模式非常相似,很多人甚至把它们看成一种模式。但它们设计目的不同:
    代理模式并不实现和目标对象类似的业务功能,而是一些与目标对象功能联系不太紧密的职责,也就是说,二者处理的是不同的问题域。比如权限控制,连接服务器,处理异常,缓存目标对象等,这

些与目标对象的功能不是同一类的;而装饰器对象处理的问题与目标对象处理的问题都是同一问题域,目的是增加目标对象那方面的能力,例如Java IO 的BufferedInputStream和修饰对象InputStream都

是为实现流读取设计的,它使得InputStream可以缓冲读取数据。
  装饰器动态地为目标对象添加功能或者减少功能,还可以递归地装饰目标对象,产生叠加的效果,而代理模式不用做此用途。

 ⑦ 适配器模式: 中国的电器拿到美国去用,如果接口不一样,则可以找一个转换器接在中间
  适配器模式和代理模式都是把请求转发给目标对象来处理,但是实现的接口不一样;代理模式的代理类和被代理类(目标类)都实现了相同的接口,而适配器实现新的满足客户类的接口(目标接口),

实现兼容客户类的目的。


  ⑧ 外观 (Facade) 模式   【外观模式用到了一条非常有用的设计原则:最少知识原则 Least Knowledge Principle 】
         在软件设计里,如果软件系统和一些子系统有非常繁复的交互,你可能希望减少和他们之间的交互达到简化的目的,这就使用到了外观模式。外观模式是我们最常使用的设计模式之一,非常直观

和简单,读者可能已经在使用了,只不过不知道它就是外观模式。


   ⑨ 组合模式。【将对象组合成树形结构来表现部分-整体的层次关系。组合使得客户一致地使用单个对象和组合对象。】
    我们在开发过程中经常使用到树形结构,它分为叶子节点和分支节点,客户对象在使用这两种对象时候经常要对他们加以区别,增加了代码的复杂度,也非常容易出错。组合模式围着两种类型提供了

同一的接口,可以让我们像操作叶子节点那样方便地操作分支节点。

    对于组合模式来说有两种,我们把对子节点的管理操作(如add,remove和getChild等操作)放在分支类BranchComposite类里,叶子类BranchLeaf看不到这些操作和定义,这种是安全的组合模式,另

外一种叫做透明的安全模式:可以把管理子节点的操作放在 Component,然后让叶子类的这些操作在运行时失效(可以不做任何处理,也可以抛出异常等),这样对客户对象来说,这种方式更加透明。

第四章 (行为模式)包括策略(strategy)模式,状态(State)模式和 观察者(Observer)模式。
  ⑩策略模式。 我们先给出一条OOP关于封装变化的设计原则:找出你的应用程序变化的那些方面,并把他们和那些不变的部分分开。
   GoF 给出的策略模式定义:定义了一组算法,对每一种进行封装,使他们可以相互交换。此模式可以使算法独立于使用它的客户程序而变化。
  注意 OOP的另一条设计原则: 优先使用合成而非继承。 OOP的一个重要特点是继承,继承提供了一条避免代码重复的途径,尽管如此,我们还是优先考虑合成,HappyPeople对象合成了Travellable的对

象,这比继承来的更加灵活。

  ⑾  状态(State)模式
  GoF 给出的定义是:允许一个对象在其内部状态发生变化时改变自己的行为,该对象看起来好像修改了它的类型


  (14)观察者模式(Observer)
      在股价波动,通知股票持有者的模型中,股票和投资者之间的关系是1:N的关系,当价格发生一定变化时,股票以广播的方式通知投资者,这正是观察者模式,GoF给出的定义如下:
     定义了对象之间的一种一对多的依赖关系,这样,当一个对象的状态发生变化时,所有依赖于对象都被通知并自动更新。
      上述模式采用的是push方式,即股票把观察者所需要的信息都推给了他们,这里是指把股票价格都推给了目标。另外一种pull方式,即股票在通知观察者时并不传递股票信息,而由观察者显式地查

询股票信息。使用前者时,如果观察者需要获得更多的信息,可能导致观察者接口不能重用;使用后者时,观察者可能不容易知道哪些信息发生了改变。

 

设计模式终结篇
    OOP,它不能解决所有的编程问题,OOP在纵向分解问题方面表现十分出色,但对于水平问题,有点无能为力,AOP能够弥补这方面的不足。我们将在15章介绍时下流行的AOP技术。
    了解和使用模式对于开发来说,还远远不够,软件的核心是模型,开发大型的复杂业务的应用,需要精炼的模型,如果模型不够精炼,也不能享受到设计模式带来的好处,在 16章我们将讲解简单介绍

如何提炼模型解决复杂的领域问题。

 

【总述】
  面向对象的开发范式:对象是包含了方法和数据的结构体,这给开发者带来了新的视角去分析问题:
     面向对象的最大好处就是封装,让我们再回顾一下这些设计模式都封装了哪些方面:
   (1)创建型模式:创建型模式封装了实例化的过程,客户对象不再关心这些创建的细节;应该是用哪个具体类,如何初始化和组装实例。这样,也为实例化提供了很大的灵活性:可以使用克隆的方式

加快创建过程和简化创建细节,也可以根据配置确定实例化的具体类型,还可以根据需要创建的单例对象。
   (2)模板方法模式:封装了那些不变的算法步骤,把变化那些部分交给子类去封装,对使用者隐藏了具体的子类型。
   (3)装饰器模式:装饰器模式隐藏了被装饰对象;并且,由于装饰对象和被装饰的对象具有相同的接口,客户对象在使用被装饰过的对象和未装饰过的对象时,不需要对他们区别对待,隐藏了装饰器

类型。
   (4)代理模式:和装饰器模式一样,隐藏了被代理对象(目标对象),也实现了代理类型的隐藏。
   (5)适配器模式:隐藏了被适配的接口/类,客户对象并不知道请求会转发给被适配的对象。
   (6)外观模式:隐藏了子系统,封装了外观和子系统之间的复杂交互。
   (7)组合模式:实现了叶子类和分支子类的隐藏,客户对象操作叶子对象和分支对象时不需要区别对待。
   (8)策略模式:每一具体策略都封装了一个具体的实现算法。
   (9)状态模式:每一状态都封装了与一个特定状态相关的行为,Context隐藏了状态接口和实现,客户对象不知道它们的存在。
   (10)观察者模式:不同的观察者对变化的处理是不同的,把这些变化封装在不同类型的观察者类型里,由于他们有相同的接口,观察者就能独立于主题而变化。另外如果为主题也抽象了接口,这样观

察者和主题两方面就能独立变化与重用,而不会影响对方。

 【OOP强调面向接口和抽象的编程,隐藏了具体的实现,减少了耦合】对象模块化的粒度超过了单个的方法或者结构体,这样,我们更容易把系统分割成易于处理的抽象单元,我们也可以重用和组合这些

功能更强的单元,并隐藏更多的细节,减少程序之间的耦合。   多态可以让我们关心在什么时间去做,而不关心如何去做,因为如何去做可以在后续开发中扩展。多态强调以相同的方式处理不同类型的对

象,而这些对象有什么样的行为却在运行时决定。
  

      一些 软件设计原则
  DRY原则:   尽量避免重复,如果你能在你的代码里发现重复,尽快的重构它。
  好莱坞原则、控制反转:   这个原则让我们可以很轻松的把可扩展的那部分代码插入到控制程序中去,以完成一个完整的流程,这样,我们就更关注于需要扩展的那部分逻辑开发了。
  OCP原则:   软件实体要对修改关闭,对扩展开放,软件开发发展至今,在这方面取得了一些成就。
  最少知识原则:   知道的最少,耦合性最弱。
  封装变化:   封装那些变化的部分供客户化,让使用者依赖于抽象而非具体,在不影响使用者的前提下扩展系统功能。
  优先使用合成而非继承:   尽管继承是一个非常好的重用父类功能、扩展父类功能的方法,但是我们尽量使用合成,有时候二者结合。
  Pareto原则:   这条原则不仅仅是我上述讲述的几种表达方式,可以广泛运用到软件开发的其他方面。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值