存档

‘设计模式’ 分类的存档

转载:极速理解设计模式系列【目录索引】

2011年10月31日 admin 2 条评论
分类: 设计模式 标签:

关键是抽象——抽象和面向对象设计

2011年4月9日 admin 没有评论

什么是抽象

什么是抽象。以下这段内容载录自百度百科。

抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征。例如苹果、香蕉、生梨、葡萄、桃子等,它们共同的特性就是水果。得出水果概念的过程,就是一个抽象的过程。要抽象,就必须进行比较,没有比较就无法找到在本质上共同的部分。

抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征。例如苹果、香蕉、生梨、葡萄、桃子等,它们共同的特性就是水果。得出水果概念的过程,就是一个抽象的过程。要抽象,就必须进行比较,没有比较就无法找到在本质上共同的部分。

共同特征是指那些能把一类事物与他类事物区分开来的特征,这些具有区分作用的特征又称本质特征。因此抽取事物的共同特征就是抽取事物的本质特征,舍弃非本质的特征。所以抽象的过程也是一个裁剪的过程,将不同的、非本质性的特征全部裁剪掉了

所谓的共同特征,是相对的,是指从某一个刻面看是共同的。比如,对于汽车和大米,从买卖的角度看都是商品,都有价格,这是他们的共同的特征,而从其他方面来比较是,他们则是不同的。所以在抽象时,同与不同,决定于从什么角度上来抽象。抽象的角度取决于分析问题的目的。

抽象化主要是为了使复杂度降低,以得到论域中较简单的概念,好让人们能够控制其过程或以综观的角度来了解许多特定的事态。

关键是抽象

“关键是抽象”这个章节载录自《敏捷软件开发原则、模式与实践》的第九章 开放-封闭原则的9.3小节——关键是抽象。

象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。它可以忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。

在c++、Java或是其他任何的OOPL中,可以创建出固定却能够描述一组任意个可能行为的抽象体。这个抽象体就是抽象基类。而这组任意个可能行为的则表现为可能的派生类。

模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,所以它对于更改是可以关闭的。同时通过这个抽象体派生,也可以扩展此模块的行为。

参考文档

分类: 设计模式 标签:

转载:笛米特法则详解(the Law of Demeter or Principle of Least Knowledge)

2010年1月10日 admin 没有评论

原文地址:笛米特法则详解(the Law of Demeter or Principle of Least Knowledge)

The Law of Demeter和 Principle of Least Knowledge讲的都是一回事,是说一个软件实体要尽可能的只与和它最近的实体进行通讯。通常被表述为:talk only to your immediate friends ( 只和离你最近的朋友进行交互)。  “talk”其实就是对象间方法的调用。这条规则表明了对象间方法调用的原则:

(1) 调用对象本身的方法;

(2) 调用通过参数传入的对象的方法;

(3) 在方法中创建的对象的方法;

(4) 所包含对象的方法。

上面的4点看起来有点别扭,下面通过一个具体的例子,就可以对上述4条guideline有进一步感性的认识:

 demeter_code

下面对start()方法中的语句进行分析:

第10行-key.turns():符合上述的第(2)条,key对象是通过参数传入start()方法的。

第13行-engine.start():符合上述的第(4)条,engine对象是包含在Car的对象之中的。

第14行-UpdateDashboardDisplay():符合上述的第(1)条,UpdateDashboardDisplay()方法是Car对像自身的方法。

第15行-doors.lock():符合上述的第(3)条,doors对象是在start()方法中创建的对象。

接下来看一个违反Principle of Least Knowledge的例子:

1 public float getTemp() {
2   Thermometer thermometer = station.getThermometer();
3   return thermometer.getTemperature();
4 }

上面的方法中station对象是immediate friends。但是上面的代码却从station对象中返回了一个Thermometer对象,然后调用了thermometer对象的getTemperature()方法,违反了Principle of Least Knowledge

下面对上面的方法作出符合Principle of Least Knowledge的改进:

1 public float getTemp() {
2  return station. getTemperature();
3 }

我们在Station类中添加一个方法getTemperature()。这个方法将调用Station类中含有的Thermometer对象的getTemperature()。这样getTemp()方法就只知道Station对象而不知道Thermometer对象。

总结:笛米特法则告诉我们要尽量只和离自己最近的对象进行交互。离自己最近的对象包括:自身包含的对象,方法中创建的对象,通过参数传进的对象,还有自己本身。

参考资料

Breaking the Law of Demeter is Like Looking for a Needle in the Haystack

分类: 设计模式, 软件设计 标签:

Separated Interface分离接口模式

2009年12月27日 admin 没有评论

在一个包定义接口,而在另一个与这个包分离的包中实现这个接口。

需要对两个系统之间进行解藕时,可以使用Separated Interface。当并不提倡对每个类都使用Separated Interface。保持接口和实现的分离需要一些额外的工作,只有当你希望打破这种依赖关系的使用才使用它,或者同一个接口有多个实现才使用。当然,也可以把接口和实现放在一起,当需要分离的时候才进行重构。

参考资料

Separated Interface

Patterns of Enterprise Application Architecture 476页

分类: 设计模式, 软件设计 标签:

Intercepting Filter模式

2009年11月28日 admin 没有评论

Intercepting Filter模式的用途

Intercepting Filter模式通常用在某些消息需要应该被处理但目标处理应该包装到任意的一组前置、后置处理流程中例如消息的转换、转义等等。

Intercepting Filter模式的构成

通常由5个基本的组件组成:

1.FilterManager 
       2.FilterChain
       3.IFilter interface
       4.Context
       5.ITarget interface

InterceptingFilter_classes

FilterManager

FilterManager类是Intercepting Filter模式的边界,客户程序在特定的上下文(由Context接口定义)通过FilterManager处理目标操作(由ITarget接口定义)。另外,FilterManager负责FilterChain的创建、初始化和执行。

FilterChain

FilterChain维护一组filter和Target。

IFilter

IFilter接口只有一个方法,先做前置处理,如果处理没有问题就让FilterChain去处理接下来的filter。如果中途发生了错误Filter返回或抛出异常。执行完一系列Filter以后就执行target的处理,然后以同样的方式返回,Filter可以做一些后置处理。基本的流程如下图

InterceptingFilter_basicFlow

下面这张图表示的是当某个Filter中出现错误或异常的时候,就不执行Target的处理,而直接返回FilterChain中进行后置操作。

InterceptingFilter_exceptionalFlow

ITarget

ITarget接口实现类是消息的接受者或者包装者。

Context

Context应该是相当抽象的,它可以使不同的类或者是一组类,或是Map。例如在Core J2EE Patterns – Intercepting Filter的实现中Context是ServletRequest和ServletResponse。

 

参考资料

Intercepting Filter Design Pattern

Core J2EE Patterns – Intercepting Filter

MSDN – Intercepting Filter

Interceptor模式

2009年11月28日 admin 没有评论

模式的用途

Interceptor模式有着广泛的用途,例如:

  • 监控应用程序的工作状态
  • 提供了改变应用程序行为的可能性
  • 提供了为应用程序扩展新特性的可能性
    • 实现应用的扩展而不需要知道应用的其他部分
    • 实现应用程序而不需改变现有的代码
    • 新的扩展能够或者不能够影响当前的系统

 

Intecetor模式的构成

Interceptor模式有3个基础的组件

1.IInterceptor 接口
       2.Dispatcher
       3.Context

InterceptorClassDiagram[1]

 

IInterceptor 接口可以定义任意数量的接口方法供Dispatcher调用。通常每个方法都只有一个参数Context。IInterceptor 有各种实现登录、授权、参数验证等等。

Dispatcher通常也实现IInterceptor 接口。有多种方式可以实现分派功能,例如Dispatcher根据某种优先级调用Intecetor或者当Interceptor抛出异常时标志整个拦截过程的结束。

Context用来承载供Interceptor使用的数据和保存由Interceptor产生的数据。除了不同的IInterceptor可能会有不同的Context 实现,同一个IInterceptor不同的方法也会有不同的实现。

InterceptorSequenceDiagram[2]

 

1.IInterceptor

  • 一个或多个方法
  • 针对多种方法有一种或多种Context类型
  • 执行的结果传递给Dispatcher
    • Dispatcher依据Context的状态确定下一步操作。
    • Disaptcher对不同的Exception进行不同的处理。

2.Dispatcher

  • Dispatcher可以实现或者不实现IInterceptor接口。
  • 分派的机制可以有多种(区分优先顺序、随即、依赖Context…)。

 

参考资料

Interceptor Design Pattern

The Interceptor Pattern

分类: 设计模式 标签:

Observer观察者模式以其应用

2009年10月3日 admin 没有评论

定义

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

使用的原因

在应用程序开发过程中,往往都要求用户界面和业务逻辑分离,划定清晰的界限.因为应用程序要求能快速的更改用户界面并且不能对应用程序其他部分产生连带影响,而且业务逻辑也会发生变化并要求这一切变化与用户界面无关.观察者(observer)就是解决此问题最常用的设计模式,它非常有助于在系统中各个对象之间划分清晰的界限。

还有一种常见的情况,一个软件系统常常要求在某一个对象发生变化的时候,某些其它的对象做出相应的改变,而观察者模式是解决这个问题的一个低耦合度的设计方案,它能够减少对象之间的耦合以便更加利于系统的复用。

未命名

用JDK中的Observable接口和Observer类实现Observer模式

由于Observer模式使用范围很广,所以在JDK中提供了Observable接口和Observer类方便开发人员使用Observer模式,而且使用也很简单。

  • 创建被观察者类,它继承自java.util.Observable类。
  • 创建观察者类,它实现java.util.Observer接口;
  • 对于被观察者类,添加它的观察者:

void addObserver(Observer o)

当被观察事件发生时,执行:

setChanged();

notifyObservers();

setChange()方法用来设置一个内部标志位注明数据发生了变化;notifyObservers()方法会去调用观察者对象列表中所有的Observer的update()方法,通知它们数据发生了变化。只有在setChange()被调用后,notifyObservers()才会去调用update()。

  • 对于观察者类,实现Observer接口的唯一方法update

void update(Observable o, Object arg)

形参Object arg,对应一个由notifyObservers(Object arg);传递来的参数,当执行的是notifyObservers();时,arg为null。

public class NumsObservable extends Observable {

public final static Integer ODD = 1;

public final static Integer EVEN = 2;

private int data = 0;

public int getData() {

  return data;

}

public void setData(int i) {

data = i;

Integer flag = EVEN;

if ((data & 0×0001) == 1)

  flag = ODD;

  setChanged();

notifyObservers(flag);

}

}

public class OddObserver implements Observer {

public void update(Observable o, Object arg) {

if (arg == NumsObservable.ODD) {

NumsObservable myObserable = (NumsObservable) o;

System.out.println("OddObserver:Data has changed to " + myObserable.getData());

}

}

S}

 

import java.util.Observable;

import java.util.Observer;

public class EvenObserver implements Observer {

public void update(Observable o, Object arg) {

if (arg == NumsObservable.EVEN) {

NumsObservable myObserable = (NumsObservable) o;

System.out.println("EvenObserver:Data has changed to " + myObserable.getData());

}

}

}

 

public class MultiTest {

public static void main(String[] args) {

NumsObservable number = new NumsObservable();

number.addObserver(new OddObserver());

number.addObserver(new EvenObserver());

number.setData(1);

number.setData(2);

number.setData(3);

}

}

在Spring框架中使用Observer模式

上面这个例程虽然很简单,但相信都大家了解了Observer模式的原理,而在实际的项目过称中,Observer模式的运用却复杂很多。例如,Observer对象本身还需要有很多依赖才能完成功能。下面这个例程针对的是如何在Spring框架下使用Observer模式,相信通过这段代码可以帮大家解决不少Observer模式使用的实际问题。

Subject接口代码

public interface Subject {
    void addListener(Observer observer);

    void removeListener(Observer observer);

    void notifyListeners();
}

Subject的具体实现

public class ConcretSubject implements Subject {
    private List<Observer> listeners=new ArrayList<Observer>();

    public void addListener(Observer observer) {
        listeners.add(observer);

    }

    public void notifyListeners() {
        for (Observer observer : listeners) {
            observer.update();
        }

    }

    public void removeListener(Observer observer) {
        listeners.remove(observer);

    }

}

Observer接口代码

public interface Observer {

    void update();

}

Observer接口的具体实现

public class ConcretObserver implements Observer{

    public void update() {
        System.out.println("ConcretObserver is updated");
    }

}

以上内容都是很普通的Java类,跟【用JDK中的Observable接口和Observer类实现Observer模式】的代码样例很类似,不同的是【用JDK中的Observable接口和Observer类实现Observer模式】的这个样例中Observer是以代码的形式注册到Observerable实现类中的,而在这个例程中借助Spring框架的MethodInvokingFactoryBean我们用配置的方式来实现以上的功能。

<beans>
    <bean id="subject" class="com.gaoke.simpleerp.service.impl.ConcretSubject" />
    <bean id="observer" class="com.gaoke.simpleerp.service.impl.ConcretObserver" />
    <bean id="registerConcretObserver"
        class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetObject">
            <ref local="subject" />
        </property>
        <property name="targetMethod">
            <value>addListener</value>
        </property>
        <property name="arguments">
            <list>
                <ref bean="observer" />
            </list>
        </property>
    </bean>
</beans> 

上面这个例程介绍的是如何通过Spring提供的MethodInvokingFactoryBean来实现Observer模式。由于Spring提供了强大的IOC容器功能,过Spring容器来实现Observer模式会让我们的应用程序开发的时候能更随心所欲:-)。

参考

Java:应用Observer接口实践Observer

Spring Loaded Observer Pattern

面向对象设计、设计模式、代码维护、重构、单元测试关系的思考笔记

2009年9月12日 admin 没有评论

这篇文章是我个人对面向对象设计、设计模式、代码维护、重构、单元测试之间关系的一些思考笔记,内容写的比较粗糙,在以后会不断地完善文章的内容、和示例。

例如,我们有一个场景需要实现在某个人给Blog发了一个Comment以后,就发一份邮件给Blog的host,在邮件中说明comments的内容,外加一个批准这个Comment的url link。

一个典型的实现可能如下面这段代码

public class CommentAction {
    private CommentService commentService;

    private MailSender mailSender;

  /**

   发布评论方法。

  */

    public void doComment(Rundata rundata) {

        Comment comment = getComment(rundata);
        Boolean resesult = commentService.doComment(comment);
        if (resesult) {
            Mail commentMail = genContent4CommentNotificationMail(comment);
            mailSender.sendMail(commentMail);
        }
    }

  /**

   获取评论数据

*/

    private Comment getComment(Rundata rundata) {
        Comment comment=new Comment();
        comment.setCotent((String)rundata.get("content"));
        comment.setPoster((String)rundata.get("poster"));
        comment.setTimeSubmited((Date)rundata.get("timeSubmited"));
        return comment;
    }

/**

   产生发送邮件的内容。

*/

    private Mail genContent4CommentNotificationMail(Comment comment) {
        Mail mail = new Mail();
        mail.setBody(comment.getContent());
        mail.setTitle("您的Blog有一封新的评论");
        return mail;
    }

}

我们在描述CommentAction 的功能描述应该是“向系统发表一篇Comment然后,生成提醒邮件发送给Blog的Host”。

从业务发生变化的角度来分析,以下几种业务的变更都会导致CommentAction 的频繁修改

  • 评论内容和格式的变化
  • 增加新的提醒方式
  • Email提醒的内容、格式发生变化
  • 等等

从面向对象的设计原则来分析这块代码,从“向系统发表一篇Comment然后,生成提醒邮件发送给Blog的Host”这个描述中开发人员应该体会到CommentAction 包含了至少2项功能,意味着这个类目前的实现已经破坏了单一责任原则。CommentAction 就是一个God类,包含了实现这个UC几乎全部的逻辑。

从代码维护的角度来分析这块代码,随着业务逻辑越来越复杂,相应的实现也会变得越来越复杂,代码越来越不清晰,代码的维护越来越困难。

从单元测试的角度来分析这块代码,对CommentAction 的doComment方法的测试,需要Mock commentService和MailSender 这两个类。单元测试的开发、维护都会困难。CommentAction 其中某个逻辑的修改都有可能破坏CommentAction 其他的单元测试。

所以解决的方法是对CommentAction 进行重构。我做的重构如下,仅供参考。

public class CommentAction {
    private CommentService commentService;
    private List<CommentEventListener> commentEventListeners;

    public void doComment(Rundata rundata) {
        Comment comment = getComment(rundata);
        commentService.doComment(comment);
        fireEvents(comment);
    }

    private void fireEvents(Comment comment) {
        CommentEvent commentEvent=new CommentEvent(comment);
        for (CommentEventListener commentEventListener : commentEventListeners) {
            commentEventListener.fireEvent(commentEvent);
        }
    }

    private Comment getComment(Rundata rundata) {
        Comment comment=new Comment();
        comment.setCotent((String)rundata.get("content"));
        comment.setPoster((String)rundata.get("poster"));
        comment.setTimeSubmited((Date)rundata.get("timeSubmited"));
        return comment;
    }   
}

public class CommentEvent   {

    private Comment comment;
    public CommentEvent(Comment comment) {
        this.comment=comment;
    }
    public Comment getComment() {
        return comment;
    }

}

public interface CommentEventListener {

    void fireEvent(CommentEvent commentEvent);
}

public class CommentEventMailNotifactionListener implements CommentEventListener {
    private MailSender mailSender;

    private Mail genContent4CommentNotificationMail(Comment comment) {
        Mail mail = new Mail();
        mail.setBody(comment.getContent());
        mail.setTitle("您的Blog有一封新的评论");
        return mail;
    }
    @Override
    public void fireEvent(CommentEvent commentEvent) {
        mailSender.sendMail(genContent4CommentNotificationMail(commentEvent.getComment()));
    }
}

重构以后的CommentAction中主要进行的就是comment的发布,另外就是触发了commentEvent事件。邮件的发送逻辑被移植到了CommentEventMailNotifactionListener 来实现,解除了CommentAction和MailSender 类的耦合。CommentAction 不再是个God类,实现这个UC的逻辑由几个类协作完成。

从业务发生变化的角度来分析重构以后的代码

  • 评论内容和格式的变化——只需要修改CommentAction
  • 增加新的提醒方式——只需要扩展新的CommentEventListener
  • Email提醒的内容、格式发生变化——只需要修改CommentEventMailNotifactionListener

从代码维护的角度来分析重构以后的代码,CommentEventMailNotifactionListener 、CommentAction 都只关注他们各自的逻辑,代码清晰、实现简单、维护也很容易,修改了之后对其他模块的破坏几率也很小。

从单元测试的角度来分析这块代码,对CommentAction 的doComment方法的测试,只需要Mock commentService这个类就可以了。对邮件的提醒的单元测试只需要对CommentEventMailNotifactionListener 进行单元测试就可以了。重构前后单元测试的开发、维护的难度和成本都会得到控制。

从上面这个例子中,我们可以总结出以下几点:

  • 编写易读、易维护、可扩展性好的代码是我们的最大目标。
  • 要实现这个目标需要遵循很多面向对象的设计原则。
  • 设计模式的运用可以帮助我们达到这个目标,后者说使用合理的设计模式就可以实现这个目标。
  • 实现这个目标需要通过代码的不断重构来达到。
  • 保证重构以后的代码还是可以继续工作的,就需要单元测试来保护我们的每一次重构。

Builder设计模式

2009年9月8日 admin 没有评论

工厂模式和构造函数有一个很大的限制就是:无法创建大量可选参数的对象。针对这种情况,常规的做法往往是要使用大量的构造函数来解决对象的构造问题。Builder模式针对的就是这个问题。

调用方先调用Builder类的构造函数并把必选的参数都传递给这个构造函数。然后调用方陆续调用Builder的其他可选参数。最后调用Builder类的builder方法返回所需构造的类的实例。下面是一个Builder模式的示例。

public class Customer {

  private final String name;

  private final String surname;

  private final int age;

  private final String address;

  private final String email;

   public static class Builder {

      //Mandatory parameters

    private String name;

    private String surname;

      //Optional parameters

    private int age;

    private String address;

    private String email;

      public Builder(String name, String surname) {

      this.name = name;

      this.surname = surname;

    }

      public Builder age(int val) {

      age = val;

      return this;

    }

      public Builder address(String val) {

      address = val;

      return this;

    }

      public Builder email(String val) {

      email = val;

      return this;

    }

    public Customer build() {

      return new Customer(this);

    }

  }

   private Customer(Builder builder) {

    name = builder.name;

    surname = builder.surname;

    age = builder.age;

    address = builder.address;

    email = builder.email;

  }}
 
    调用方的代码如下
Customer customer = new Customer.Builder("John", "Doe").age(25).email("johndoe@gmail.com").build();
    另外还可以在各个方法中加入验证信息,从而保证对象的有效性。
 
参考资料
http://onjavahell.blogspot.com/2009/07/introducing-builder-design-pattern.html

分类: 设计模式 标签:

什么是面相对象设计的SOLID原则(s)

2009年3月28日 admin 没有评论

S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。

SRP The Single Responsibility Principle 单一责任原则
OCP The Open Closed Principle 开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
DIP The Dependency Inversion Principle 依赖注入原则
ISP The Interface Segregation Principle 接口分离原则

单一责任原则

当需要修改某个类的时候原因有且只有一个(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。
http://www.diybl.com/course/4_webprogram/jsp/jsp_js/20090304/157704.html

开放封闭原则

软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。我对这个原则的理解还是很模糊,还需要在日后的工作、学习过程中慢慢体验:-)。
http://www.cnblogs.com/adam/archive/2008/04/18/1159280.html

里氏替换原则

当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
http://lensping.blog.sohu.com/73111028.html

依赖注入原则

1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
        2. 抽象不应该依赖于细节,细节应该依赖于抽象
http://hi.baidu.com/mickeycn/blog/item/e60900129241da56f819b884.html

接口分离原则

不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
http://blog.csdn.net/xiaolu123456789/archive/2009/02/26/3941014.aspx

其他重要的面向对象设计原则

The Principles of OOD 这篇文章中介绍了其他一些面向对象设计的原则,有兴趣的同学可以进一步的学习。

总结

这几条原则是非常基础而且重要的面向对象设计原则。正是由于这些原则的基础性,理解、融汇贯通这些原则需要不少的经验和知识的积累。希望能在日后的工作、学习过程中真正的掌握和运用这些原则。

分类: 设计模式, 软件设计 标签: , , , , ,