面向对象的设计原则

  • 格式:docx
  • 大小:40.81 KB
  • 文档页数:16

面向对象的设计原则一 开放-关闭原则

动机

一个设计良好的应用程序应该充分考虑到开发和维护阶段需求的频繁变化,通常情况下,添加一个新的功能需要做出很多修改,我们应该使对已有代码的修改最小化,因为他们已经经过了测试。对现有代码做出修改将会以一种不可预料的方式影响它们的已有功能。

开放-关闭原则 (以下简称开闭原则)

开-闭原则:

一个软件实体应该对扩展开发,对修改关闭。

开闭原则是说我们应该努力设计不需要修改的模块。在扩展系统的行为时,我们只需要添加新的代码,而不需要修改已有的代码。一般可以通过添加新的子类和重写父类的方法来实现。

满足开闭原则的模块符合下面两个标准:

 对扩展开放 ------- 模块的行为可以被扩展从而满足新的需求。

 对修改关闭 ------- 不允许修改模块的源代码。(或者尽量使修改最小化)

这两个标准看似相互矛盾的,那么我们怎么实现他们呢?

怎样实现开闭原则

 抽象

 多态

 继承

 接口

要想使一个软件系统的所有模块都满足开闭原则是不太现实的,不过我们应该努力使大部分模块满足开闭原则。开闭原则是面向对象设计的核心,满足该原则可以达到最大限度的复用和可维护性。

实例

考虑下面某个类的方法:

public double totalPrice(Part[] parts) {

double total = 0.0;

for (int i=0; i

total += parts[i].getPrice();

}

return total; }

上面函数的功能是计算给定的零件数组中所有零件价格的总和,如果Part是一个基类或者接口,那我们就可以利用多态的特性,当有新的零件被添加进来时不需要修改该函数的代码。这样它就可以满足开闭原则。

但是如果我们的会计部门规定当计算主板和内存的价格时,需要添加一些额外的费用,请看下面的代码:

public double totalPrice(Part[] parts) {

double total = 0.0;

for (int i=0; i

if (parts[i] instanceof Motherboard)

total += (1.45 * parts[i].getPrice());

else if (parts[i] instanceof Memory)

total += (1.27 * parts[i].getPrice());

else

total += parts[i].getPrice();

}

return total;

}

现在它还符合开闭原则吗?不!每次会计部门发布一个新的价格政策时,我们都需要修改totalPrice()方法!它对修改不是关闭的,显然,价格政策的改变意味着我们必须修改某处的代码,那么我们应该怎么做呢?为了使用我们第一个版本的totalPrice()方法,我们需要把Part的getPrice()方法的价格政策包含进来。

下面是Part和ConcretePrat类:

// Class Part is the superclass for all parts.

public class Part {

private double price;

public Part(double price) (this.price = price;}

public void setPrice(double price) {this.price = price;}

public double getPrice() {return price;}

}

// Class ConcretePart implements a part for sale.

// Pricing policy explicit here!

public class ConcretePart extends Part {

public double getPrice() {

// return (1.45 * price); //Premium

return (0.90 * price); //Labor Day Sale

}

} 但是,现在如果价格政策改变,我们必须修改Part的子类,一个更好的方法是建立一个PricePolicy类,它可以为我们提供不同的价格政策:

/**

* Class PricePolicy implements a given price policy.

*/

public class PricePolicy {

private double factor;

public PricePolicy (double factor) {

this.factor = factor;

}

public double getPrice(double price) {return price * factor;}

}

使用这种方法,我们可以在运行时动态的设置Part对象所引用的PricePoilcy对象,在实际的程序中,零件的价格和相关的PricePolicy可以从数据库中获取。

总结

像许多其他原则一样,开闭原则只是面向对象设计的一个原则,实现一个灵活的设计需要额外的时间和努力,引入新的抽象层会增加代码的复杂性。因此,该原则适用于那些需求会经常发生变化的系统。有许多设计模式可以帮助我们扩展功能而不需要修改代码。例如,装饰模式等。

面向对象的设计原则二-单一职责原则

动机

在本文中职责是指引起变化的原因。该原则表明,如果你有多个原因去改变一个类,那么应该把这些引起变化的原因分离开,把这个类分成多个类,每个类只负责处理一种改变。当你做出某种改变时,只需要修改负责处理该改变的类。当我们去改变一个具有多个职责的类时可能会影响该类的其他功能。

单一职责原则

一个类应该只受一种变化的影响。

单一职责原则简单而直观,但是在实际实现中可能是很困难的。

实例

假设我们需要一个对象保存email信息,在下面的例子中我们将使用IEMAIL接口。初看起来,一切都很好。但是仔细分析我们会发现我们的IEMAIL接口和Email类具有两个职责(两种引起改变的原因)。一个是在一些类似pop3和imap的email协议下使用该类,如果需要支持其他的协议,需要以其他的方式格式化内容字段,并且需要添加新的代码来支持新的协议。另一个是Content字段,尽管content字段是字符串类型,或许我们将来要支持其他的格式,例如HTML格式。

如果我们只用一个类,一个职责的改变可能会影响另一个:

 添加新的协议需要添加新的代码解析和格式化内容字段。

 添加新的内容类型(例如HTML)需要为每种已实现的协议添加代码。

//single responsibility principle - bad example

interface IEmail {

public void setSender(String sender);

public void setReceiver(String receiver);

public void setContent(String content);

}

class Email implements IEmail {

public void setSender(String sender) {

// set sender;

}

public void setReceiver(String receiver) {

// set receiver;

}

public void setContent(String content) {

// set content;

}

}

我们可以创建一个新的IContent接口和一个新的Content类来分离职责。让每一个类只承担一个职责可以给我们的设计带来更多的灵活性:

 添加新的协议时只需要修改Email类。

 添加新的内容类型时只需要修改Content类。

//single responsibility principle - good example

interface IEmail {

public void setSender(String sender);

public void setReceiver(String receiver);

public void setContent(IContent content);

}

interface IContent {

public String getAsString(); // used for serialization }

class Email implements IEmail {

public void setSender(String sender) {

// set sender;

}

public void setReceiver(String receiver) {

// set receiver;

}

public void setContent(IContent content) {

// set content;

}

}

总结

单一职责原则代表了设计应用程序时一种很好的识别类的方式,并且它提醒你思考一个类的所有演化方式。只有对应用程序的工作方式有了很好的理解,才能很好的分离职责

面向对象的设计原则三 - 接口隔离原则

动机

当我们设计应用程序的时候,如果一个模块包含多个子模块,那么我们应该小心对该模块做出抽象。设想该模块由一个类实现,我们可以把系统抽象成一个接口。但是当我们想要添加一个新的模块扩展程序时,如果要添加的模块只包含原系统中的一些子模块,那么就会强迫我们实现接口中的所有方法,并且还要编写一些哑方法。这样的接口被称为胖接口或者叫被污染的接口,使用这样的接口将会给系统引入一些不正确的行为。

接口隔离原则表明客户端不应该被强迫实现一些他们不会使用的接口,应该把胖接口中的方法分组,然后用多个接口代替它,每个接口服务于一个子模块。

接口隔离原则

不应该强迫客户端依赖于他们不会使用的接口。

实例

下面是一个违反了接口隔离原则的例子。我们使用Manager类代表一个管理工人的管理者。有两种类型的工人:普通的和高效的,这两种工人都需要吃午饭。现在来了一批机器人,它们同样为公司工作,但是他们不需要吃午饭。一方面Robot类需要实现IWoker接口,因为他们要工作,另一方面,它们又不需要实现IWorker接口,因为它们不需要吃饭。