书城计算机大话设计模式
11030400000229

第229章 323种设计模式的个人见解

大B:“我来讲讲我个人对设计模式的理解吧。”

小A:“呵呵!好啊!”

大B:“也许能让你更好地理解23种设计模式。”

1、Adapter(适配器)模式:旨在提供用户期望的接口,以便利用具有不同接口的类的服务。

(1)个人理解:实际上只是把客户调用,转变为调用已经存在的方法。适配器的作用可以理解为提供一个人人皆知的,顾名思义的新方法名。

(2)提示代码:

public double getMass(){

return rocket。getMass(simTime);

}

2、Facade(外观)模式:旨在为子系统提供一个接口,使之更加容易使用。

(1)经典范例:JOptionPane类,JOptionPane。showConfirmDialog(……)

(2)个人理解:构建一个个目的明确的类,比如典型的静态方法的使用。

(3)提示代码:

int option;

option=JOptionPane。showConfirmDialog(……);//静态方法创建对话框

(4)提示关键字:外观类,工具类,实例类

3、Composite(组合)模式:旨在让用户能够用统一的接口处理单个对象以及对象组合。

(1)经典范例:组合,树,环

(2)个人理解:其他很多模式的基础,群组可以包含群组或者个体,群组和个体有共同的接口。

(3)提示代码:

MachineComponent mc=(MachineComponent)i。next();

count =mc。getMachineCount();

(4)提示关键字:递归

4、责任型模式Bridge(桥接)模式:旨在将依赖抽象操作的类与这些抽象操作的实现相分离,从而使得抽象类与实现能够独立变化。

(1)经典范例:驱动程序

(2)个人理解:将抽象和方法的具体实现分离,抽象类中包含一个driver对象,driver对象即是对方法的具体实现。

(3)提示关键字:装载

5、Chain of Responsibility(责任链)模式:旨在将一个方法调用请求沿着责任链依次转发给下一个对象,让每个对象都有一次机会决定自己是否处理该请求,从而降低请求的发送者与其接受者之间的耦合程度。

(1)个人理解:寻找责任的请求在链中传递,如果责任人已经找到则终止,否则继续向其他对象转发责任。

(2)提示代码:

public Engineer getResponsible(VisualizationItem item){

if(item instanceof tool){

Tool t=(Tool)item;

return t。getToolCart()。gerResponsible();

}

if(item instanceof ToolCart){

ToolCart tc=(ToolCart)item;

return tc。gerResponsible();

}

}

(3)提示关键字:转发

6、Singleton(单例)模式:旨在确保某个类只有一个实例,并且为之提供一个全局访问点。

(1)个人理解:创建一个类的唯一实例,可以作为全局变量。

(2)提示代码:

public static Factory getFactory(){

if(factory……null)

factory=new Factory();

return factory;

}

7、Observer(观察者)模式:旨在在多个对象之间定义一对多的依赖关系,以便当一个对象状态改变时,其他所有依赖这个对象的对象都能够被通知,并自动更新。

(1)经典范例:GUI(MVC中分离M和VC)

(2)个人理解:当一个对象发生改变的时候,其他关心该对象的对象能够得到通知,并且更新自身状态。

(3)提示代码:

public void notifyObservers(){

observers。update();

}

(4)提示关键字:注册,监听

8、Mediator(中介者)模式:旨在定义一个对象来封装一组对象之间交互的方式,这样可避免对象间的显示引用,而且还可以独立对这些对象的交互进行修改。

(1)经典范例:GUI(特指MVC中的controller)

(2)个人理解:中介者类专门用于处理对象间的交互,与GUI的布局组件分离

(3)提示代码:

public void setLocation(Machine value){

return mediator。set(this,value);

}

9、Proxy(代理)模式:旨在为某个对象提供一个代理来控制对该对象的访问。

(1)经典范例:图像代理(长时间载入内存前的Loading提示)

(2)个人理解:提供一个代理来承担责任(转发请求),实际操作的对象并不是根本对象,而是一个用户和真正实现之间的中间角色。

(3)提示代码:

setImage(LOADING。getImage());

callbackFrame。repaint();

new Thread(this)。start();

(4)提示关键字:占位

10、Flyweight(享元)模式:旨在通过共享来为大量的细粒度对象提供有效的支持。

(1)个人理解:很多类具有相同的且不变的属性,可以将这些属性提取出来构成享元,在一个特定的工厂类中作为内部类,具有static的get方法,便于外部类共享。

(2)提示代码:

public class ChemicalFactory{

private static Mao chemicals=new HashMap();

ChemicalImp{

//some attributes and methods

}

static{

chemicals。put(new ChemicalImp());

}

public static Chemical getChemical(String name){

return***;

}

}

(3)提示关键字:共享对象

11、Builder(生成器)模式:旨在把构造对象实例的代码逻辑移到要实例化的类的外部,以便于细化构造过程,或者简化对象。

(1)经典范例:解析文本构造对象

(2)个人理解:用一个builder类收集构造信息,在确定信息足够(或者满足构造的最低要求)的时候,再生成对象。

(3)提示代码:

String sample=“***************”;

ReservationBulider builder=new UnforgivingBuilder();

new ReservationParser(builder)。parse(sample);

Resercation res=builder。build();

(4)提示关键字:逐步构造

12、Factory Method(工厂方法)模式:旨在定义一个用于创建对象的接口,同时控制对哪个类进行实例化。

(1)经典范例:迭代器

(2)个人理解:为相关的多个类提供一个共同的接口,客户不需要知道该实例化哪个类,具体实例化的类由服务的提供者决定。

(3)提示代码:

List list=Arrays。asList(new String[]{“1”,“2”,“3”});

Iterator iter=list。iterator();

(4)提示关键字:共同接口

13、Abstract Factory(抽象工厂)模式:旨在创建一系列相互关联或相互依赖的对象。

(1)经典范例:GUI工具包

(2)个人理解:创建一系列相关的对象,也就是把创建一个大对象所需要的子操作聚合起来。

(3)提示代码:

public JButton createButtonOK(){

JButton b=super。createButtonOk();

b。setIcon(getIcon(“images/123.gif”));

return b;

}

(4)提示关键字:外观和感觉

14、Prototype(原型)模式:通过拷贝一个现有对象生成新的对象。

(1)个人理解:通过复制一个已经存在的对象,保存原来对象的状态,在此基础上进行进一步的改动。

(2)提示代码:

public OzPanel copy2(){

OzPanel result=new OzPanel();

result。setBackground(this。getBackground());

//more result。set***methods……

return result;

}

(3)提示关键字:复制

15、Memento(备忘录)模式:旨在为对象提供状态存储和状态恢复功能。

(1)经典范例:撤销操作

(2)个人理解:使用栈进行撤销和恢复的操作,栈顶部是当前的状态。更多的,可以把相关状态进行持久性存储。

(3)提示代码:

public void undo(){

if(!canUndo())return;

mementos。pop();

}

(4)提示关键字:redo,undo

16、Template Method(模板方法)模式:旨在一个方法中实现一个算法,并遵循算法中某些步骤的定义,从而使得其他类可以重新定义这些新步骤。

(1)经典范例:(根据不同规则)排序

(2)个人理解:在算法的实现中,把一些需要自定义的部分(通常是算法的核心部分),留在外部的类来实现。并可以需要实现的部分设置钩子。

(3)提示代码:

Array。sout(rockets,new ApogeeComparator());

public class ApogeeComparator implements Comparator{

//base method about sort……

}

(4)提示关键字:算法框架 算法步骤

17、State(状态)模式:旨在将与状态有关的处理逻辑分散到代表状态的各个类中。

(1)个人理解:将所有的状态都构建成一个相应的类,它们的超类对外部各个事件提供相应的同意接口,使得调用者无需判断当前状态。

(2)提示代码:

public class Door2 extends Observable{

public void touch(){

state。touch();

}

}

public class DoorOpen extends DoorState{

public void touch(){

door。setState(door。STAYOPEN);

}

}

(3)提示关键字:状态处理分散

18、Strategy(策略)模式:旨在把可选的策略或方案封装到不同的类中,并在这些类中实现一个共同的操作。

(1)个人理解:为不同的解决方案建立类,在执行的时候选择一个策略执行。与State模式比较,两者很接近,前者倾向在可选的方案中选择,后者是在不同的状态之间迁移。

(2)提示代码:

private Advisor getAdvisor(){

if(advisor……null){

if(promotionAdvisor。hasItem())

advisor=promotionAdvisor;

//maybe more else if

}

return advisor;

}

(3)提示关键字:策略选择 策略执行

19、Command(命令)模式:旨在将请求封装为一个对象,并将该请求对象作为参数;客户可以提供不同的请求对象,如队列请求,时间请求或者日志请求;也可以让客户准备调用该请求的特定上下文。

(1)经典范例:菜单命令(actionPerformed())

(2)个人理解:将方法(一般是execute()方法)封装在对象中,使用时直接调用相关command对象的execute()方法即可。可以作为Template模式的替代模式。

(3)提示代码:

Command doze=new Command(){

public void execute(){

//do something

}

}

public class CommandTimer{

Public static long time(Command command){

command。execute();

}

}

long actual=CommandTimer。time(doze);

(4)提示关键字:封装对象

20、Interpreter(解释器)模式:旨在使开发者可以根据自己定义的组合规则生成可执行的对象。

(1)个人理解:常与Command和Composite模式配合使用,对命令进行组合使用,有点像编程中使用语句构造功能。

(2)提示代码:

public class IfCommand extends Command{

protected Term term;

protected Command body;

protected Command elseBody;

public IfCommand(Term term,Command body,Command elseBody){

this。term=term;

this。body=body;

this。elseBody=elseBody;

}

public void execute(){

if(term。eval()!=null)

body。execyte();

else

elseBody。execute();

}

}

(3)提示关键字:解释器,组合对象

21、Decorator(装饰器)模式:旨在使开发者能够动态地组织对象的行为。

(1)经典范例:流和输出器,函数包装器

(2)个人理解:在运行时动态创建不同的变化

(3)提示代码:

BufferedOutputStream out=

new BufferedOutputStream(

new GZIPOutputStream(

new FileOutputStream(args[1])));

(4)提示关键字:动态组合

22、Iterator(迭代器)模式:旨在为开发人员提供一种顺序访问集合元素的方法。

(1)个人理解:在新建一个结构的时候,为顺序访问其元素,可以同样新建一个对应的迭代器类。也可以自定义访问元素的其他方式(比如逆序)。

(2)提示代码:

List employees;

ListIterator forward(employees);

ReverseListIterator backward(employees);

PrintEmployees(forward);

PrintEmployees(backward);

(3)提示关键字:访问元素

23、Visitor(访问者)模式:旨在让开发者能够在不修改现有类层次结构的前提下扩展该类层次结构的行为。

(1)个人理解:在开发类的时候,留有一个accept()操作,该操作接受一个visitor参数。在需要为类增加新的操作时,无需改变原来的类层次,直接编辑visitor中的visit操作,然后使用accept()方法接受即可。

(2)提示代码:

public class FindVisitor implements MachineVisitor{

public MachineComponet find(MachineComponet mc){

mc。accept(this);

}

public void visit(MachineComposite mc){

//do something

}

}

MachineComponent factory=OozinozFactory。dublin();

MachineComponent machine=new FindVisitor()。find(factory,3404);

(3)提示关键字:不改变类层次附录:面向对象基础

小A:“为什么要‘面向对象’?”

大B:“面向对象方法使构建系统更容易,因为:解决正确的问题,正常工作,易维护,易扩充,易重用。大家发现面向对象更易理解,实现可以更简单。把数据和功能组合在一起简单而自然,分析和实现之间的概念跨度更小,设计良好的一组对象能弹性地适应重用和变化,可视化模型提供更有效的沟通,建模过程有助于创建通用词汇以及在开发者和用户/客户之间达成共识。非计算机编程人员也能理解对象模型,这些好处可以使用面向对象方法获得,但面向对象方法不能保证这一点。”

小A:“怎样才能变成优秀的面向对象设计者?”

大B:“只有靠经验和聪明的头脑才能做到。”

过程化方法(The Procedural Approach)

小A:“怎样过程化方法?”

大B:“系统由过程(procedures)组成,过程之间互相发送数据,过程和数据各自独立,集中于数据结构、算法和运算步骤的先后顺序,过程经常难以重用,缺乏具有较强表现力的可视化建模技术,分析与实现之间需要进行概念转换,本质上是机器/汇编语言的抽象,从设计模型到代码实现跨度很大。”

面向对象方法

大B:“系统由对象组成,对象互相发送消息(过程调用)相关的数据和行为紧密地绑定在对象中,把问题领域建模成对象,要解决的问题自然的映射为代码的实现,可视模型表现力强,相对容易理解,集中于实现之前所确定的职责(responsibilities)和接口。强有力的概念:接口,抽象,封装,继承,委托(delegation)和多态。问题的可视模型逐渐进化成解决方案模型,设计模型与代码实现之间跨度较小努力缩减软件的复杂度。”

温度换算

大B:“下面我就以温度换算为例。”

过程/函数化方法

float c=getTemperature();//假定为摄氏度。

float f=toFarenheitFromCelcius(c);

float k=toKelvinFromCelcius(c);

float x=toKelvinFromFarenheit(f);

float y=toFarenheitFromKelvin(k);

面向对象方法

Temp temp=getTemperature();

float c=temp。toCelcius();

float f=temp。toFarenheit();

float k=temp。toKelvin();

包含有数据的Temp的内部单元是什么?

建模(Modeling)

小A:“成功的程序能解决真实世界的问题。”

大B:“嗯,是的。它们紧密对应于需要解决的问题。对问题领域和用户活动进行建模。”

小A:“建模促进与用户更好的可视化交流。”

大B:“成功的面向对象设计总是一开始就由领域专家和软件设计者建立一个反映问题领域的可视化的‘对象模型’。”

小A:“嗯。是的。”

大B:“你愿意让承包人在没有设计蓝图的情况下建造你的新房子吗?”

小A:“那当然不行啦。”

对象

大B:“你知道怎样去理解什么是对象吗?”

小A:“对象代表真实或抽象的事物,有一个名字,有明确的职责(well-defined responsibilities),展示良好的行为(well-defined behavior),接口清晰,并且尽可能简单、自相容,内聚,完备(self-consistent,coherent,and complete)。”

大B:“嗯,对。(通常)不是很复杂或很大,只需要理解自己和一小部分其他对象的接口,与一小部分其它对象协同工作(team players),尽可能地与其它对象松散耦合(loosely coupled),很好地文档化,以便他人使用或重用,对象是类的实例,每一个对象都有唯一的标识,类定义一组对象的接口和实现,即定义了这些对象的行为,抽象类不能拥有实例,只要有抽象类(如宠物),通常就会有能够实例化的具体类(如猫,狗等),一些面向对象语言(如Smalltalk)支持元类(metaclass)的概念,程序员可以随时(on-the-fly)定义一个类,然后实例化。这种情况下,类也是一个对象,即元类。对象一旦实例化,就不能更改它的类。”

对象的特征

大B:“那你知道对象有什么特征吗?”

小A:“有唯一标识,可以分成许多种类(即类),可以继承或聚合。行为、职责明确,接口与实现分离,隐藏内部结构,有不同的状态,可以提供服务,可以给其它对象发送消息,从其它对象接收消息,并做出相应响应,可以把职责委托给其它对象。”

大B:“对,说得非常全面。”

小A:“怎么样才叫类呢?”

大B:“有公共的属性和行为的一组对象可以抽象成为类,对象通常根据你所感兴趣的属性而分类。”

小A:“喔。”

大B:“例如:街道,马路,高速公路……不同的程序对它们分类也不同。交通模拟器程序,单行道,双通道,有分车道的,住宅区的,限制通行的维护调度程序,路面材料,重型卡车运输类本身也可以有属性和行为。例如:养老金管理程序中的‘雇员’类雇员总数,雇员编制多少,不同语言对类的支持略有不同:Smalltalk 把类当作对象(很有好处),C 提供最小限度的支持(有时会带来很多烦恼),Java位于上述两者之间,类也是对象,类可以有属性‘雇员’类可以有一个包含其所有实例的列表(list)‘彩票’类可以有一个种子(seed)用于产生随机票号,该种子被所有实例共享,类可以有行为,雇员”类可以有getEmployeeBySerialNum 行为。‘彩票’类可以有generateRandomNumber 行为。

封装

大B:“只暴露相关的细节,即公有接口(public interface)。”

小A:“封装什么?如何封装?”

大B:“隐藏‘齿轮和控制杆’只暴露客户需要的职责,防止对象受到外界干扰,防止其它对象依赖可能变化的细节,信息隐藏有助于对象和模块之间的松散耦合,使得设计更加灵活,更易于重用,减少代码之间的依赖,‘有好篱笆才有好邻居’。例如:汽车的气动踏板。”

小A:“怎样才能更好地实践?”

大B:“最佳实践:对象之间只通过方法(函数)互相访问。切忌直接访问属性。”

class Person{

public int age;

}

class BetterPerson{

private int age;//change to dateOfBirth

public int getAge(){return age;}

}

更完善的Person 类可能是:private dateOfBirth

抽象

小A:“什么是抽象?”

大B:“抽象使得泛化(generalizaions)成为可能,简化问题-忽略复杂的细节,关注共性,并且允许变更,人类经常使用泛化。当你看见约翰和简家里的那头灰德国牧羊犬时,你有没有……想到‘狗’这个词?抽象同样能简化计算机程序。例如,软件中有两个重要抽象:客户端和服务器(clients and servers)。”

小A:“喔。”

大B:“在图形用户界面中,系统可能会询问用户各种问题:是或不是多选一?输入数字,任意文本问题统一处理这些问题会显得很简单,每一个问题都作为Question类的特例(specialization);程序只需维护这些问题的实例列表,分别调用各自的askTheUser()方法。”

继承

小A:“什么是继承?”

大B:“继承用于描述一个类与其它类的不同之处。例如:类Y像类X,但有下列不同……”

小A:“为什么使用继承?”

大B:“你有两种类型,其中一种是另一种的扩展。有时(不是所有时候)你想忽略对象之间的不同,而只关注它们的共同之处(基类)。这就是泛化。假如某系统需要对不同的形状进行操作(经典例子):有时你并不关心你正在操作的形状的种类(例如,移动形状时)有时你必须知道形状的种类(在显示器上绘制形状)”

小A:“怎样去理解派生类?”

大B:“派生类继承自基类;派生类扩展了基类;派生类是基类的特殊化(specialization)。派生类能够提供额外的状态(数据成员),或额外的行为(成员函数/方法),或覆盖所继承的方法。基类是所有它的派生类的泛化。如:通常所有宠物都有名字。基类(Base Class)=父类(parent class)=超类(superclass)派生类(Derived Class)=子类(child class)=子类(subclass)”

小A:“喔。”

大B:“继承含有(有些,不是全部)是一个(is-a)或是一种(is-a-kind-of)的关系,正方形是一种矩形(使用继承),Leroy 是一种狗(不使用继承),传统的过程分析和设计中不能很好地模拟这种关系。继承是一种强有力的机制,使我们关注共性,而不是特定的细节。使得代码可以重用且富有弹性(能适应变化)。”

小A:怎样去实现继承?

大B:“实现继承(Implementation inheritance):派生类继承基类的属性和行为。”

小A:“又应该怎样去接口继承?”

大B:“接口继承(Interface inheritance):类实现抽象接口的方法,保留既定语义(intended semantics)C 允许多重实现继承。Java规定派生类只能有一个基类,但可以继承自多个接口。”

多态

小A:“什么是多态?”

大B:“多态是一种允许多个类针对同一消息有不同的反应的能力。对于任何实现了给定接口的对象,在不明确指定类名的情况下,就可以使用。例如:question。askTheUser();当然,这些不同反应都有类似的本质 尽可能使用接口继承和动态(运行期)绑定Liskov 替换原则:如果Y是X的子类,那么在任何使用X实例的地方都可以用Y的实例来替换。”

演示多态的Java代码

//File:question/QuestionTest。java

//下面的代码将输出什么?

//Refer to the Beginning Java link on the course web site。

package question;

abstract class Question{//Full class name is question。QuestionTest

public Question(Stringtext){//Constructor

theText=text;

}

public abstract void askTheUser();

protected String theText;

}

class YesNoQuestion extends Question{

public YesNoQuestion(Stringtext){super(text);}

public void askTheUser(){

System。out。println(theText);

System。out。println(“YES or NO……?”);

}

}

class FreeTextQuestion extends Question{

public FreeTextQuestion(Stringtext){super(text);}

public void askTheUser(){

System。out。println(theText);

System。out。println(“Well……?Whats the answer……?”);

}

}

public class QuestionTest{

public static void main(String[]args){

Question[]questions=getQuestions();

for(int i=0;i<questions。length;i ){

questions[i]。askTheUser();//Polymorphism!

}

}

private static Question[]getQuestions(){

Question[]qs=new Question[2];

qs[0]=new YesNoQuestion(“Do you understand polymorphism?”);

qs[1]=new FreeTextQuestion(“Why is polymorphism good?”);

return qs;

}

}

输出:

Do you understand polymorphism?

YES or NO……?

Why is polymorphism good?

Well……?Whats the answer……?

更多的Java例子

//File:Derived。java

//What will the following Java code output to the screen?

class Base{

final void foo(){

System。out。println(“Base foo”);

}

void bar(){

System。out。println(“Base bar”);

}

}

public class Derived extends Base{

void bar(){

System。out。println(“Derived bar”);

}

public static void main(String[]args){

Derived d=new Derived();

d。foo();

d。bar();

Base b=d;

b。bar();

}

}

输出:

Base foo

Derived bar

Derived bar

为什么面向对象有效

小A:“为什么面向对象有效?”

大B:“首先是减小复杂度。”

小A:“嗯。”

大B:我们从封装、多态、继承、委托来具体说。

小A:“喔?”

大B:“封装:只暴露公有接口,隐藏了复杂的实现细节,避免代码之间复杂的相互依赖。多态:允许有相同接口的类互相替换,由此减小代码的复杂度。继承:使用抽象类或接口实现泛化来减小复杂度。委托:通过从更小、封装更好的服务来构建更完整或更高层次的服务来减小复杂度。委托还增加了运行时的灵活性。”

小A:“面向对象有效我们是不是还可以从语言学和辨识角度来说?”

大B:“是的。我们主要使用名词,然后对它进行修饰和增加属性,最后,把它和动词联合在一起。面向对象设计遵循这个模式,过程化设计不遵循这个模式,这就是为什么人们经常发现对象更容易理解。我们从对象模型中能形成构造良好的主谓宾(Subject-verb-object)句子:人们拥有宠物。Paula 拥有Leroy。试试用功能分解来形成主谓宾句子!而且,人们广泛使用抽象和泛化……”

面向对象是编程进化一个自然阶段

小A:“为什么说面向对象是编程进化一个自然阶段?”

大B:“首先出现机器语言。在此基础上发展出汇编语言,提供了符号。高级语言出现:Fortran,Pascal,C等。它们提供了程序语句之间的‘结构’关系。‘goto’的使用日渐稀少,这有助于简化程序结构。数据结构和算法提供了程序结构的可重用模式,促进更高层次上的抽象。面向对象的抽象是为了关注于解决问题,而不是机器。通过更高层次的抽象,程序语句之间的关系转化成为相对简单的对象协作关系,设计模式提供可重用的对象结构……”

面向对象更多的好处

大B:“面向对象还有更多的好处。”

小A:“是吗?都还有些什么好处哩?”

大B:“组件非常有用,代码重用。设计模式很好,设计重用。接口不错,灵活健壮的代码。底层结构(infrastructure)和可重用服务同样很好。接口能完美分离个人和团队的职责,增加团队效率,松散耦合和模块化提高了扩展性、灵活性、可量测性和重用性。逻辑变化很自然的被隔离起来,这多亏了对象的模块化和信息隐藏(封装)。这意味着实现更快,维护更容易。面向对象中间件使我们无须关注位置、平台和语言。设计良好的对象是我们可以增加新功能而不用更改设计。”

好的面向对象设计

小A:“什么样的是好的面向对象设计?”

大B:“艺术多于科学。可解决问题的模型本质上当然没有问题,但是一些模型就是比其它的好,这是因为它们更实用、更灵活、更容易扩展、更方便理解、更简单……第一个设计几乎不可能是最好的设计。找到最好的抽象来对问题建模始终不是一件容易的事情,经验很重要。”

小A:“经常需要尝试多次,来确定如何划分系统各部分之间的边界才是最好?每一部分应该为其它部分提供什么接口?”

大B:“以体系结构为中心,而不是功能为中心。首先关注全面的大体的,其次才是具体的特定的。设计时首先做到这一点,就成功了一大半。设计中要考虑可能发生的扩展,使得以后扩展是递增式的,不用更改设计。不同的设计可以有完全相同的功能,但是在这方面可能完全不同。尽量推广可重用的面向服务的底层结构,这样,在需求不可避免的变化时,代码也能更快、更容易的更改。”

职责

小A:“什么是职责?”

大B:“是面向对象分析中采用的最普遍的方法。基于‘客户端/服务器’关系,对‘客户端/服务器’有两种通用的解释:用于分布式体系中,服务器提供对共享资源(如数据库)的访问,客户端提供用户界面。用于面向对象术语中,服务器是一个提供服务的对象;在这里我们使用这个含义,客户端与服务器协作(发送消息)。一个对象可能在一个协作中是客户端,而在另一个协作中是服务器。服务器负责提供某种服务,一般来说,对象应该以某种定义良好的方式工作。”

设计过程概述

大B:“我们讲了这么多,你知道设计过程是什么吗?”

小A:“查看领域,识别对象、类。通常首先识别出对象,通过对象分组找到类,确定对象之间和类之间的关系,结构关系,协作关系,赋予职责,基于协作关系,迭代,迭代,迭代,迭代,迭代,迭代……以领域建模作为开始,而不是以建模解决方案作为开始。”

CRC卡片

小A:“什么是CRC卡片?”

大B:CRC方法使用3×5(英寸)索引卡片,一个类就是一张卡片,卡片上写有该类的职责以及为了完成这些职责必须与哪些类协作。类的简要描述写在卡片背面。

例子:“棍子”游戏

游戏设计两个玩家使用一台计算机来一起玩。游戏中许多棍子按行排列,当游戏开始时,它们如下排列:

1:

2:

3:

4:

游戏规则

玩家轮流参加,每人可以从任何一个非空行中移走一根或多根棍子。移走最后一根棍子的人为输家。

游戏开始时,程序将显示游戏的状态:轮到谁了,还有几行,还有多少棍子。

操作不符合规则,程序将给出提示。(如所移走的棍子数目超过该行的棍子总数)

找到对象和类……

用CRC卡片

附加问题:哪个类负责记录轮到那个玩家了?

词汇(Vocabulary)

类(Class)

抽象(Abstract)/具体(Concrete)/元(Meta)

对象(Object)

实例(Instance)

标识(Identity)

属性(Attribute)

成员(Member)

域(Field)

状态(State)

行为(Behavior)

方法(Method)

成员函数(Member Function)

操作(Operation)

职责(Responsibility)

消息(Message)

调用方法(Method Call)

接口(Interface)

抽象(Abstraction)

泛化(Generalization)

特殊化(Specialization)

继承(Inheritance)

接口(Interface)/实现(Implementation)

基类(Base)/派生类(Derived),父类(Parent)/子类(Child),超类(Super)/子类(Sub)

委托(Delegation)

协作(Collaboration)

多态(Polymorphism)

Liskov 替换原则(Liskov Substitution Principle)

动态绑定(Dynamic(run-time)Binding)

聚合(Aggregation)

底层结构(Infrastructure)

服务(Services)/中间件(Middleware)/框架(Frameworks)

统一建模语言(Unified Modeling Language(UML))

分析(Analysis)/设计(Design)/实现(Implementation)/架构(Architecture)/过程(Process)

松散耦合(Loose Coupling&Flexibility)

封装(Encapsulation)

信息隐藏(Information Hiding)

模块性(Modularity)

透明(Transparency)

构造函数(Constructor)/包(Package)/静态(Static)

模式(Patterns)

职责驱动设计(Responsibility driven design)

设计模式与面向对象

小A:“面向对象设计模式主要是解决什么问题哩?”

大B:“面向对象设计模式解决的是类与相互通信的对象之间的组织关系,包括它们的角色、职责、协作方式几个方面。面向对象设计模式是‘好的面向对象设计’”。

小A:“什么是‘好的面向对象设计’?”

大B:“所谓‘好的面向对象设计’是那些可以满足‘应对变化,提高复用’的设计。”

小A:“这么说来,面向对象设计模式主要都是讲些什么哩?”

大B:面向对象设计模式描述的是软件设计,因此它是独立于编程语言的,但是面向对象设计模式的最终实现仍然要使用面向对象编程语言来表达,基于C#语言,但实际上它适用于支持。NET框架的所有。NET语言,如Visual Basic、。NET、C /CLI等。面向对象设计模式不像算法技巧,可以照搬照用,它是建立在对‘面向对象’纯熟、深入的理解的基础上的经验性认识。掌握面向对象设计模式的前提是首先掌握‘面向对象’!从编程语言直观了解面向对象,各种面向对象编程语言相互有别,但都能看到它们对面向对象三大机制的支持,即:‘封装、继承、多态。’

封装,隐藏内部实现

继承,复用现有代码

多态,改写对象行为

使用面向对象编程语言,可以推动程序员以面向对象的思维来思考软件设计结构,从而强化面向对象的编程范式。C#是一门支持面向对象编程的优秀语言,包括:各种级别的封装支持;单实现继承 多接口实现;抽象方法与虚方法重写。但OOPL并非面向对象的全部。通过面向对象编程语言(OOPL)认识到的面向对象,并不是面向对象的全部,甚至只是浅陋的面向对象。OOPL的三大机制‘封装、继承、多态’可以表达面向对象的所有概念,但这三大机制本身并没有刻画出面向对象的核心精神。换言之,既可以用这三大机制做出‘好的面向对象设计’,也可以用这三大机制做出‘差的面向对象设计’。不是使用了面向对象的语言,就实现了面向对象的设计与开发!因此我们不能依赖编程语言的面向对象机制,来掌握面向对象。

大B:“OOPL没有回答面向对象的根本性问题――我们为什么要使用面向对象?我们应该怎样使用三大机制来实现‘好的面向对象’?我们应该遵循什么样的面向对象原则?”

大B:“任何一个严肃的面向对象程序员(例如Java程序员),都需要系统地学习面向对象的知识,单纯从编程语言上获得的面向对象知识,不能够胜任面向对象设计与开发。”

从一个示例谈起

示例场景:

我们需要设计一个人事管理系统,其中的一个功能是对各种不同类型的员工,计算其当月的工资――不同类型的员工,拥有不同的薪金计算制度。

结构化做法

1、获得人事系统中所有可能的员工类型

2、根据不同的员工类型所对应的不同的薪金制度,计算其工资

enumEmployeeType

{

Engineer;

Sales;

Manager;

……

}

//计算工资程序

if(type……EmployeeType。Engineer)

{

……

}

else if(type……Employeetype。Sales)

{

……

}

面向对象设计

1、根据不同的员工类型设计不同的类,并使这些类继承自一个Employee抽象类,其中有一个抽象方法GetSalary。

2、在各个不同的员工类中,根据自己的薪金制度,重写(override)GetSalary方法。

abstract class Employee

{

……

public abstract int GetSalary();

}

class Engineer:Employee

{

……

public override int GetSalary()

{

……

}

}

class Sales:Employee

{

……

public override int GetSalary()

{

……

}

}

//显示工资程序

Employee e=emFactory。GetEmployee(id);

MessageBox。Show(e。GetSalary());

示例场景:

现在需求改变了……随着客户公司业务规模的拓展,又出现了更多类型的员工,比如钟点工、计件工……等等,这对人事管理系统提出了挑战――原有的程序必须改变。

结构化做法,几乎所有涉及到员工类型的地方(当然包括“计算工资程序”)都需要做改变……这些代码都需要重新编译,重新部署……面向对象做法,只需要在新的文件里增添新的员工类,让其继承自Employee抽象类,并重写GetSalary()方法,然后在EmployeeFactory。GetEmployee方法中根据相关条件,产生新的员工类型就可以了。其他地方(显示工资程序、Engineer类、Sales类等)则不需要做任何改变。重新认识面向对象,对于前面的例子,从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小。从微观层面来看,面向对象的方式更强调各个类的“责任”,新增员工类型不会影响原来员工类型的实现代码――这更符合真实的世界,也更能控制变化所影响的范围,毕竟Engineer类不应该为新增的“钟点工”来买单……

小A:“对象是什么?”

大B:“从概念层面讲,对象是某种拥有责任的抽象。从规格层面讲,对象是一系列可以被其他对象使用的公共接口。从语言实现层面来看,对象封装了代码和数据。”

小A:“有了这些认识之后,怎样才能设计‘好的面向对象’?”

大B:“遵循一定的面向对象设计原则熟悉一些典型的面向对象设计模式。从设计原则到设计模式,针对接口编程,而不是针对实现编程。客户无需知道所使用对象的特定类型,只需要知道对象拥有客户所期望的接口。优先使用对象组合,而不是类继承。类继承通常为‘白箱复用’,对象组合通常为‘黑箱复用’。继承在某种程度上破坏了封装性,子类父类耦合度高;而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。封装变化点,使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。使用重构得到模式――设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。敏捷软件开发实践提倡的‘Refactoring to Patterns’是目前普遍公认的最好的使用设计模式的方法。”

小A:“有没有更具体的原则?”

大B:“有啊!我和你说说几条更具体的设计原则。单一职责原则(SRP):一个类应该仅有一个引起它变化的原因。开放封闭原则(OCP):类模块应该是可扩展的,但是不可修改(对扩展开放,对更改封闭)。Liskov 替换原则(LSP):子类必须能够替换它们的基类。依赖倒置原则(DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于实现细节,实现细节应该依赖于抽象。接口隔离原则(ISP):不应该强迫客户程序依赖于它们不用的方法。”

小A:“这样就好记多了。”

大B:“设计模式描述了软件设计过程中某一类常见问题的一般性的解决方案。面向对象设计模式描述了面向对象设计过程中、特定场景下、类与相互通信的对象之间常见的组织关系。深刻理解面向对象是学好设计模式的基础,掌握一定的面向对象设计原则才能把握面向对象设计模式的精髓,从而实现灵活运用设计模式。”

小A:“嗯,我记住了。”

大B:我再给你最后讲讲三大基本面向对象设计原则:1、针对接口编程,而不是针对实现编程。2、优先使用对象组合,而不是类继承。3、封装变化点,使用重构得到模式。

JAVA常识

大B:“师弟,我来给你介绍一些JAVA的常识,这样对你以后学习JAVA有帮助。”

小A:“嘿嘿!好啊!”

大B:“你最好就好好地记住它。”

1、jave的方法中的所有变量都必须初始化之后才能使用,否则无法通过编译,提示no initialize。而在方法外的变量则会被自动初始化,可以在该“{}”中使用。所有的变量都仅在自己声明的“{}”中起作用。

2、java的包分类:lang(构成语言的核心包)、awt(抽象图形工具包)、applet(已封装的applet小程序类)、io(基本的输入输出类)、net(与网络编程相关的类)、util(实用程序包,包括随机生成数字等)。

3、java中摒弃了C/C 中的指针与存储管理等应用,从而提高了程序的健壮性,防止内存漏洞与存储器漏洞。

4、用加号“ ”进行字符串连接。

5、java中的boolean类型不能与int类型进行转换。

6、java的条件控制语句(if())中,括号中使用的是布尔表达式,而不是C/C 使用的数字值。因为java中布尔类型不能与数字类型转换,因而“if(x)”这种写法是错误的,应改为“if(x!=0)”。

7、switch()语句中的条件必须是与int类型是扶植兼容的,byte、short、char类型可以被升级,不允许使用浮点和long表达式。

8、注意label与break和continue等跳出语句的使用。break label/continue label,跳转到label出继续执行。

9、java中数组是一组同种数据类型的集合,是一种对象,声明是不分配内存空间,只创建了该对象的一个引用,数组元素的实际内存空间是通过new()方法进行初始化而动态分配的。数组声明的两种方法:char[]s 或者char s[]。

10、声明可以不指出数组的大小。

11、java支持多维数组,不但支持矩阵型数组,而且支持非矩阵型数组。

12、java中具有数组拷贝函数(System。arraycopy())。

13、子类从超类(父类)继承所有方法和变量;但子类不从超类继承构造函数;包含构造函数的两个办法是a、使用缺省构造函数,b、写一个或多个显式构造函数。

14、多态性是个运行时问题,与重载相反,重载是一个编译时问题。

15、关键字super 可被用来引用该类中的超类。它被用来引用超类的成员变量或方法,可以使用super。method()的格式来调用。

16、instanceof的使用,在对象强制类型转换时常常使用。

17、在一个类中可以通过参数个数/参数类型不同,从而构造重载函数。在子类与父类之间,可以在子类中定义与父类具有一样函数名称、参数个数、参数类型、返回类型的函数,从而达到函数覆盖的作用。

18、通过子类的数据初始化父类的成员变量,可以在子类的构造函数中使用super()来调用父类的构造函数初始化父类中的成员变量。

19、import语句必须先于所有类的声明。import 语句被用来将其它包中的类带到当前名空间。当前包,不管是显式的还是隐含的,总是当前名空间的一部分。

20、类中的static变量可以被该类的所有实例共享,如果声明为private,只有该类的实例才能访问,如果声明为public,可以不通过该类的实例,直接在类体外通过类名就可以访问。

21、类中的static方法可以不通过该类的实例,直接在类体外通过类名就可以访问,static 方法不能访问与它本身的参数以及static 变量分离的任何变量。访问非静态变量的尝试会引起编译错误。没有this 值。

22、静态方法不能被覆盖成非静态。

23、static block。“static{}”。

24、final 类不能被分成子类;final 方法不能被覆盖;final 变量是常数。被标记为static 或private 的方法被自动地final,因为动态联编在上述两种情况下都不能应用。如果变量被标记为final,其结果是使它成为常数。想改变final 变量的值会导致一个编译错误。

25、声明方法的存在而不去实现它的类被叫做抽象类。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。

26、接口是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。

27、内部类不能声明任何static 成员;只有顶层类可以声明static 成员。

28、finally 语句定义一个总是执行的代码块,而不考虑异常是否被捕获。如果终止程序的System。exit()方法在保护码内被执行,那么,这是finally 语句不被执行的唯一情况。这就暗示,控制流程能偏离正常执行顺序,比如,如果一个return 语句被嵌入try 块内的代码中,那么,finally 块中的代码应在return 前执行。

29、java中类的成员变量是在对象实例化之后才分配内存空间,类变量则在类加载的时候分配空间,该类以及该类的实例对象都共享类变量。类的成员方法是在类的第一个对象实例化的时候才分配入口地址的,当再创建对象时,不再分配入口地址,就是说,所有对象共享一个入口地址,而类方法则是在类被加载到内存的时候分配入口地址的,因而可以被类以及类的实例所调用。

30、this指向本类,可以通过成员访问运算符。访问类的成员变量或方法,但this不能出现在类方法中,因为类方法是可以直接通过类名调用。

31、a、public b、protected c、友好的(无修饰符)d、private,private在子类中无法继承父类的成员变量与成员方法,在同包中,子类可继承父类的a b c类型的变量与方法,不同包中,子类只能继承a b类型的变量与方法。

32、当子类与父类之间如果存在同名的成员变量时,则父类的成员变量被隐藏,当子类与父类存在返回类型、参数类型以及个数都相同的函数时,父类的成员方法被隐藏,因此可以通过重写子类的成员函数而将父类的状态和行为改变为自身的状态与行为。但子类重写方法时,访问权限不能低于父类的修饰符。

33、如果一个方法被修饰为final方法,则这个方法不能被重写,如果一个成员变量被修饰为final的,就是常量。

34、对象的上转型对象,A是B的父类,A a=new B(),则a是b的上转型对象,上转型对象不能操作子类新增的成员变量,但可以操作子类继承或重写的成员变量,也可以使用子类继承的或重写的方法,不可以将父类创建的对象的引用赋值给子类声明的对象。

35、接口用interface声明,用于java多继承,接口体中包含常量定义和方法定义两部分,接口体中只进行方法的声明,不许提供方法的实现,所以方法的定义没有方法体,且用分号。

36、如果一个类使用了某个接口,那么这个类必须实现该接口的所有方法。如果一个类声明实现一个接口,但没有实现接口中的所有方法,那么这个类必须是abstract类。

37、接口回调:可以把实现某一接口的类创建的对象的引用赋值给该接口声明的接口变量中,那么该接口变量就可以调用被类实现的接口中的方法。当接口变量调用被类实现的接口的方法时,就是通知相应的对象调用接口的方法。

38、string类型变基本类型,可以用public int Integer。parseint(string s)其他基本类型相似,基本类型变string类型,可以用string。valueof(int/char/float/double),对象类型用date。toString()转换。

39、stringtokenizer类可以实现字符串的分析,stringtokenizer objectname=new stringtokenizer(s,“分隔符列表 用空格间隔”),重要方法:counttoken()、nexttoken()、hasmoretokens()。

40、charactor类重要方法:isDight(char)isLetter(char)isLetterOrDight(char)isLowerCase(char)isUpperCase(char)toLowerCase(char)toUpperCase(char)isSpacechar(char)。

41、将字符串转变成字符数组,可以使用string类的String。toCharArray()方法,char a[]=String。toCharArra()

42、string(char[],int offset,int length)返回一个string对象,getChars(int start,int end,char c[],int offset)功能:字符串变字符数组,由string对象使用。

</questions。length;i ){