-
Effective Java -----#1 考虑用静态工厂方法代替构造函数
2011-10-12 16:32:35
静态工厂方法具有名字。每次被调用的时候,不要求非得创建一个新的对象。可以返回原返回类型的子类型的对象。- valueOf
- getInstance
-
设计模式解读之一: 策略模式
2011-10-12 16:06:33
当我们掌握了Java的语法,当我们了解了面向对象的封装、继承、多态等特性,当我们可以用Swing、Servlet、JSP技术构建桌面以及Web应用,不意味着我们可以写出面向对象的程序,不意味着我们可以很好的实现代码复用,弹性维护,不意味着我们可以实现在维护、扩展基础上的代码复用。一把刀,可以使你制敌于无形而于江湖扬名,也可以只是一把利刃而使你切菜平静。Java,就是这把刀,它的威力取决于你使用的方式。当我们陷入无尽无止重复代码的泥沼,当我们面临牵一发而动全身的维护恶梦, 你应该想起“设计模式”这个行动秘笈。面向对象的精义,看似平淡,其实要经过艰苦实践才能成功。而构造OO系统的隐含经验于是被前人搜集而成并冠以“设计模式”之名。我们应该在编码行动初始就携带以它。接下来,让我们步“四人组”先行者之后,用中国文字、用实际案例领略模式于我们代码焕然一新的改变:
设计模式解读之一: 策略模式
1. 模式定义
把会变化的内容取出并封装起来,以便以后可以轻易地改动或扩充部分,而不影响不需要变化的其他部分;
2. 问题缘起
当涉及至代码维护时,为了复用目的而使用继承,结局并不完美。对父类的修改,会影响到子类型。在超类中增加的方法,会导致子类型有该方法,甚至连那些不该具备该方法的子类型也无法免除。示例,一个鸭子类型:
public abstract class Duck {
//所有的鸭子均会叫以及游泳,所以父类中处理这部分代码
public void quack() {
System.out.println("Quack");
}
public void swim() {
System.out.println("All ducks float, even decoys.");
}
//因为每种鸭子的外观是不同的,所以父类中该方法是抽象的,由子类型自己完成。
public abstract void display();
}
public class MallardDuck extends Duck {
//野鸭外观显示为绿头
public void display() {
System.out.println("Green head.");
}
}
public class RedHeadDuck extends Duck {
//红头鸭显示为红头
public void display() {
System.out.println("Red head.");
}
}
public class RubberDuck extends Duck {
//橡皮鸭叫声为吱吱叫,所以重写父类以改写行为
public void quack() {
System.out.println("Squeak");
}
//橡皮鸭显示为黄头
public void display() {
System.out.println("Yellow head.");
}
}
上述代码,初始实现得非常好。现在我们如果给Duck.java中加入fly()方法的话,那么在子类型中均有了该方法,于是我们看到了 会飞的橡皮鸭子,你看过吗?当然,我们可以在子类中通过空实现重写该方法以解决该方法对于子类型的影响。但是父类中再增加其它的方法呢?
通过继承在父类中提供行为,会导致以下缺点:
a. 代码在多个子类中重复;
b. 运行时的行为不容易改变;
c. 改变会牵一发动全身,造成部分子类型不想要的改变;
好啦,还是刚才鸭子的例子,你也许想到使用接口,将飞的行为、叫的行为定义为接口,然后让Duck的各种子类型实现这些接口。这时侯代码类似于:
public abstract class Duck {
//将变化的行为 fly() 以及quake()从Duck类中分离出去定义形成接口,有需求的子类中自行去实现
public void swim() {
System.out.println("All ducks float, even decoys.");
}
public abstract void display();
}
//变化的 fly() 行为定义形成的接口
public interface FlyBehavior. {
void fly();
}
//变化的 quack() 行为定义形成的接口
public interface QuackBehavior. {
void quack();
}
//野鸭子会飞以及叫,所以实现接口 FlyBehavior, QuackBehavior
public class MallardDuck extends Duck implements FlyBehavior, QuackBehavior{
public void display() {
System.out.println("Green head.");
}
public void fly() {
System.out.println("Fly.");
}
public void quack() {
System.out.println("Quack.");
}
}
//红头鸭子会飞以及叫,所以也实现接口 FlyBehavior, QuackBehavior
public class RedHeadDuck extends Duck implements FlyBehavior, QuackBehavior{
public void display() {
System.out.println("Red head.");
}
public void fly() {
System.out.println("Fly.");
}
public void quack() {
System.out.println("Quack.");
}
}
//橡皮鸭不会飞,但会吱吱叫,所以只实现接口QuackBehavior
public class RubberDuck extends Duck implements QuackBehavior{
//橡皮鸭叫声为吱吱叫
public void quack() {
System.out.println("Squeak");
}
//橡皮鸭显示为黄头
public void display() {
System.out.println("Yellow head.");
}
}
上述代码虽然解决了一部分问题,让子类型可以有选择地提供一些行为(例如 fly() 方法将不会出现在橡皮鸭中).但我们也看到,野鸭子MallardDuck.java和红头鸭子RedHeadDuck.java的一些相同行为代码不能得到重复使用。很大程度上这是从一个火坑跳到另一个火坑。
在一段程序之后,让我们从细节中跳出来,关注一些共性问题。不管使用什么语言,构建什么应用,在软件开发上,一直伴随着的不变的真理是:需要一直在变化。不管当初软件设计得多好,一段时间之后,总是需要成长与改变,否则软件就会死亡。
我们知道,继承在某种程度上可以实现代码重用,但是父类(例如鸭子类Duck)的行为在子类型中是不断变化的,让所有子类型都有这些行为是不恰当的。我们可以将这些行为定义为接口,让Duck的各种子类型去实现,但接口不具有实现代码,所以实现接口无法达到代码复用。这意味着,当我们需要修改某个行为,必须往下追踪并在每一个定义此行为的类中修改它,一不小心,会造成新的错误。
设计原则:把应用中变化的地方独立出来,不要和那些不需要变化的代码混在一起。这样代码变化引起的不经意后果变少,系统变得更有弹性。
按照上述设计原则,我们重新审视之前的Duck代码。
1) 分开变化的内容和不变的内容
Duck类中的行为 fly(), quack(), 每个子类型可能有自己特有的表现,这就是所谓的变化的内容。
Duck类中的行为 swim() 每个子类型的表现均相同,这就是所谓不变的内容。
我们将变化的内容从Duck()类中剥离出来单独定义形成接口以及一系列的实现类型。将变化的内容定义形成接口可实现变化内容和不变内容的剥离。其实现类型可实现变化内容的重用。这些实现类并非Duck.java的子类型,而是专门的一组实现类,称之为"行为类"。由行为类而不是Duck.java的子类型来实现接口。这样,才能保证变化的行为独立于不变的内容。于是我们有:
变化的内容:
//变化的 fly() 行为定义形成的接口
public interface FlyBehavior. {
void fly();
}
//变化的 fly() 行为的实现类之一
public class FlyWithWings implements FlyBehavior. {
public void fly() {
System.out.println("I'm flying.");
}
}
//变化的 fly() 行为的实现类之二
public class FlyNoWay implements FlyBehavior. {
public void fly() {
System.out.println("I can't fly.");
}
}
-----------------------------------------------------------------
//变化的 quack() 行为定义形成的接口
public interface QuackBehavior. {
void quack();
}
//变化的 quack() 行为实现类之一
public class Quack implements QuackBehavior. {
public void quack() {
System.out.println("Quack");
}
}
//变化的 quack() 行为实现类之二
public class Squeak implements QuackBehavior. {
public void quack() {
System.out.println("Squeak.");
}
}
//变化的 quack() 行为实现类之三
public class MuteQuack implements QuackBehavior. {
public void quack() {
System.out.println("<< Slience >>");
}
}
通过以上设计,fly()行为以及quack()行为已经和Duck.java没有什么关系,可以充分得到复用。而且我们很容易增加新的行为, 既不影响现有的行为,也不影响Duck.java。但是,大家可能有个疑问,就是在面向对象中行为不是体现为方法吗?为什么现在被定义形成类(例如Squeak.java)?在OO中,类代表的"东西"一般是既有状态(实例变量)又有方法。只是在本例中碰巧"东西"是个行为。既使是行为,也有属性及方法,例如飞行行为,也需要一些属性记录飞行的状态,如飞行高度、速度等。
2) 整合变化的内容和不变的内容
Duck.java将 fly()以及quack()的行为委拖给行为类处理。
不变的内容:
public abstract class Duck {
//将行为类声明为接口类型,降低对行为实现类型的依赖
FlyBehavior. flyBehavior;
QuackBehavior. quackBehavior;
public void performFly() {
//不自行处理fly()行为,而是委拖给引用flyBehavior所指向的行为对象
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys.");
}
public abstract void display();
}
Duck.java不关心如何进行 fly()以及quack(), 这些细节交由具体的行为类完成。
public class MallardDuck extends Duck{
public MallardDuck() {
flyBehavior=new FlyWithWings();
quackBehavior=new Quack();
}
public void display() {
System.out.println("Green head.");
}
}
测试类:
public class DuckTest {
public static void main(String[] args) {
Duck duck=new MallardDuck();
duck.performFly();
duck.performQuack();
}
}
在Duck.java子类型MallardDuck.java的构造方法中,直接实例化行为类型,在编译的时侯便指定具体行为类型。当然,我们可以:
1) 我们可以通过工厂模式或其它模式进一步解藕(可参考后续模式讲解);
2) 或做到在运行时动态地改变行为。
3) 动态设定行为
在父类Duck.java中增加设定行为类型的setter方法,接受行为类型对象的参数传入。为了降藕,行为参数被声明为接口类型。这样,既便在运行时,也可以通过调用这二个方法以改变行为。
public abstract class Duck {
//在刚才Duck.java中加入以下二个方法。
public void setFlyBehavior(FlyBehavior. flyBehavior) {
this.flyBehavior=flyBehavior;
}
public void setQuackBehavior(QuackBehavior. quackBehavior) {
this.quackBehavior=quackBehavior;
}
//其它方法同,省略...
}
测试类:
public class DuckTest {
public static void main(String[] args) {
Duck duck=new MallardDuck();
duck.performFly();
duck.performQuack();
duck.setFlyBehavior(new FlyNoWay());
duck.performFly();
}
}
如果,我们要加上火箭助力的飞行行为,只需再新建FlyBehavior.java接口的实现类型。而子类型可通过调用setQuackBehavior(...)方法动态改变。至此,在Duck.java增加新的行为给我们代码所带来的困绕已不复存在。
该是总结的时侯了,让我们从代码的水中浮出来,做一只在水面上自由游动的鸭子吧:
3. 解决方案
MallardDuck 继承 Duck抽象类; -> 不变的内容
FlyWithWings 实现 FlyBehavior接口; -> 变化的内容,行为或算法
在Duck.java提供setter方法以装配关系; -> 动态设定行为
以上就是策略模式的实现三步曲。接下来,让我们透过步骤看本质:
1) 初始,我们通过继承实现行为的重用,导致了代码的维护问题。 -> 继承, is a
2) 接着,我们将行为剥离成单独的类型并声明为不变内容的实例变量并通过 -> 组合, has a
setter方法以装配关系;
继承,可以实现静态代码的复用;组合,可以实现代码的弹性维护;使用组合代替继承,可以使代码更好地适应软件开发完后的需求变化。
策略模式的本质:少用继承,多用组合 -
Template Method模板方法模式
2011-10-12 14:01:17
行为模式:
行为型模式涉及到算法和对象间职责的分配。将注意力从控制流转移到对象间的联系方式上来。
行为模式分为:行为类模式和行为对象模式。行为类模式使用继承机制在类间分派行为;行为对象模式使用对象复合而不是继承。
Template Method模板方法模式
Template Method模板方法模式介绍:
Template Method模板方法模式是一种行为型模式,具体点说是行为类型模式。主要解决在软件构建过程中,对于某一项任务,常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因而无法和任务的整体结构同时实现。
GoF《设计模式》中说道:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
Template Method模式的结构
定义场景
单看结构不好理解,我们来结合程序进行分析。现在定义一个场景:我想大家都玩过极品飞车(我相当喜欢)。游戏中有很多的汽车,但是从操作角度看大同小异。无非是起步(StartUp)、行驶(Run)、停车(Stop)等等行为。汽车行驶的动作之间可能存在着差异,例如:手动档和自动档,但是接口是一样的,换句话说,基本都是这几个动作。结合Template Method模式讲就是在这个程序中,结构(对汽车的操作)是稳定的,但是变化在于各个子步骤(操作行为的具体实现)。
程序实现与结构分析
首先我们需要一个汽车的抽象类(结构图中的AbstractClass)
public abstract class AbstractCar
{
protected abstract string StartUp();
protected abstract string Run();
protected abstract string Stop();
public void DriveOnTheRoad()
{
Console.WriteLine(StartUp());
Console.WriteLine(Run());
Console.WriteLine(Stop());
}
}
在这段代码中,抽象方法StartUp、Run、Stop叫做primitive operation(原语操作),它们是在子类中的扩展点,例如我们要编写一辆BORA的实现,在他继承AbstractCar后,可以在这几个primitive operation上进行自己个步骤的实现。
AbstractCar中的DriveOnTheRoad方法叫做template method(模板方法),template method用primitive operation定义一个算法,是相对稳定的部分。(子类中重新定义primitive operation)。
然后我们要实现一辆BORA汽车(结构图中的ConcreteClass)
public class BORA : AbstractCar //继承
{
protected override string StartUp()
{
return "BORA is StartUp";
}
protected override string Run()
{
return "BORA is Running";
}
protected override string Stop()
{
return "BORA is Stoped";
}
}
接下来我们进行客户程序的实现
class Program
{
static void Main(string[] args)
{
ClientApp.Drive(new BORA());
Console.Read();
}
}
public class ClientApp
{
public static void Drive(AbstractCar car)
{
car.DriveOnTheRoad();
}
}
运行结果如下:
BORA is StartUp
BORA is Running
BORA is Stoped
Template Method要点
1、 Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为了很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
2、 除了可以灵活应对子步骤的变化外,“Don't call me.Let me Call you”的反向控制结构是Template Method的典型应用。“Don’t call me.Let me Call you”是指一个父类调用一个子类的操作,而不是相反。
3、 在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。可以没有具体实现的方法应该称为hook operation(钩子操作),提供了缺省的行为,子类可以在必要时进行扩展。
4、 尽量减少primitive operation,因为需要重新定义的操作越多,客户程序就越长 -
设计模式:利用Command模式实现无限次数的Undo/Redo功能
2011-10-12 10:43:02
几乎现在所有的文档式应用程序中,都提供了恢复/撤消功能,如Word,Excel,还有我们的写Java程序用的JBuilder。
在早期,许多的应用程序还只能提供单一的Undo/Redo,自进入90年代以来,随着OOP及Design Pattern的流行,实现无限次数的Undo/Redo编辑功能已不是难事。
每一个编辑动作我们可以把它当成一个命令,如cut,del等,在做每一个编辑动作前,我们先要保存旧的数据,以便于undo它。我们为这些编辑命令设计了如下一个通用接口:
public Interface Command
{
public void execute();
public void undo();
}
其中execute用来执行命令,undo用来恢复(undo).
接下来实现这个接口,先来实现Cut命令:
public class CutCommand implements Command
{
public void execute()
{
/* 备份旧数据 */
/* 剪切 */
}
public void undo()
{
/* 还原成备份的数据 */
}
}
再来实现一个Delete命令:
public class DeleteCommand implements Command
{
public void execute()
{
/* 备份旧数据 */
/* 删除 */
}
public void undo()
{
/* 还原成备份的数据 */
}
}
OK,我们就只列出了cut和del两个命令,其它的命令相似的写法.
这样的话我们每做一个编辑动作,就执行一个相应的command.接下来我们要考虑如何将这些执行过的命令保存下来,以实现undo/redo. 我们再设计一个CommandManager:
public Interface CommandManager
{
public void storeCommand(Command cmd);
public void clearAllCommand();
public void undo();
public void redo();
}
再来实现一个CommandManager, 我们称作CommandHistoryManager:
public class CommandHistoryManager implements CommandManager
{
Vector undoList=new Vector();
Vector redoList=new Vector();
public void storeCommand(Command cmd)
{
undoList.add(cmd);
}
public void clearAllCommand()
{
undoList.clear();
redoList.clear();
}
public void undo()
{
if ( undoList.size() <= 0 ) return;
Command cmd = ((Command)(undoList.get(undoList.size()-1)));
cmd.undo();
undoList.remove(cmd);
redoList.add(cmd);
}
public void redo()
{
if ( redoList.size() <= 0 ) return;
Command cmd = ((Command)(redoList.get(redoList.size()-1)));
cmd.execute();
redoList.remove(cmd);
undoList.add(cmd);
}
}
通过storeCommand()方法,每次执行的command就可以保存到undoList中,假设再在我们在程序中放置了两个按钮,一个undo,一个redo.按下undo按钮,就执行CommandHistoryManager的undo()方法,undo()方法会调用undoList中保存的最后一个command的undo()方法,并将这个command再放到redoList中,最后从undoList中删除这个命令。这时如果再按redo按钮,它会调用CommandHistoryManager中的redo()方法。redo()方法会调用redoList中保存的最后一个command的execute()方法,并将这个command又存回到undoList中。这样就实现了无限次数的undo/redo功能。 -
面向对象类设计的五大原则(一)单一职责原则Single Responsibility Principle
2011-09-29 12:43:00
引言
面向对象类设计,或者说是面向对象设计,有五大原则:
- Single Responsibility Principle 单一职责原则
- Open Closed Principle 开闭原则
- Liskov Substitution Principle 里氏替换原则
- Dependency Inversion Principle 依赖反转原则
- Interface Segregation Principle 接口隔离原则
简介
今天我们要接触的是SRP,单一职责原则。
我觉得这些原则的目的就是要实现类的高内聚、低耦合。高内聚high cohision和低耦合low couple在很多时候都是我们的目标,不管我们引入设计模式,进行架构分析、设计、提高复用性,解耦decouple是我们共同的目标。
There should never be more than one reason for a class to change.
类的改变不应该由一个以上的原因导致,也就是说不应该有一个以上的原因可以导致类的改变。
如果类的职责超过一个,这些职责之间就会产生耦合。改变一个职责,可能会影响和妨碍类为其他人服务的功能。这种类型的耦合将会导致脆弱的设计,在修改的时候可能会引入未逾期的问题。
如上图所示,Rectangle类有两个方法:一个是在屏幕上画矩形,一个是计算矩形的面积。两个不同的应用会使用Rectangle类,一个应用使用Rectangle来帮助计算面积,它从来不会在屏幕画矩形。另外一个是图形界面的应用,也可能会计算面积,但是肯定会在屏幕画矩形。
当前的设计就违反了SRP原则,Rectangle类有两个职责,一个是为数学模型提供几何的矩形,计算面积;另外一个职责是为图形用户界面提供矩形的显示。
这个违反SRP原则的设计会导致很多严重的问题。首先,在计算几何应用中必须包含GUI。因为计算几何应用可能就是一个控制台程序,不需要用户界面,但是由于需要使用这个违反SRP原则的类,就必须要包含GUI的类库,在编译的时候要同时编译,在部署的时候还增加了部署的内容,而且这些内容可能永远都不会被用到。
其次,如果图形应用的变化,需要修改Rectangle类的话。我们的计算几何应用也必须要重新编译和部署,因为它也引用了相同的Rectangle类,如果不那么做的话,可能会发生意外的错误(因为Rectangle类被修改了,不知道有没有什么影响)。
好一点的做法是将Rectangle分解为两个类,如下图所示
将计算面积部分从Rectangle类中分离出来,产生一个GeometricRectangle类,专门用来计算面积。这样的话,对于画矩形方法的修改就不会影响到计算几何应用了。也不用在部署多余的内容。什么是职责?在上文中,我们将单一职责定义为“一个改变的原因”。如果你想出超过一个改变类的动机,就说明类具有多个职责。这很难以发现。我们习惯于按照组来思考职责。例如,考虑下面的Modem接口。interface IModem
{
public void Dial(string no);
public void Hangup();
public void Send(char c);
public char Reveive();
}但是,这里其实是两个职责。一个职责是连接管理,另外一个是数据通信。Dial和Hangup方法管理moden的链接,Send和Receive管理modem的数据通信。这两个职责是否应该分开呢?毫无疑问是应该的。这两个职责是没有关系的。他们改变的原因不尽相同。而且,他们会被应用的不同部分调用,这两个部分也会因为不同的原因变化。上图的做法可能更好,将接口分为两个。然后有一个类来实现这两个接口,代表一个modem。结论SRP原则是最简单的原则之一,也是最难做好的原则之一。我们会很自然的将职责连接在一起。找到并且分离这些职责是软件设计需要达到的目的。【Blog】http://virusswb.cnblogs.com/
【MSN】jorden008@hotmail.com
【说明】转载请标明出处,谢谢
标题搜索
我的存档
数据统计
- 访问量: 13078
- 日志数: 32
- 建立时间: 2007-07-26
- 更新时间: 2012-12-27