`
crud0906
  • 浏览: 136528 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

结合设计模式理解多用组合少用继承的原则(转)

    博客分类:
  • OOP
阅读更多
刚刚接触模式或者学习模式的人,经常会有这样的问题,为什么模式是成功的呢?很多人都会说模式是经验的积累,当然是正确的。可是经验为什么偏偏就证明了这种模式是正确的呢?这其中起用作的就是面向对象的基本原则。正是因为模式都或多或少的符合了面向对象的基本原则,所以模式才成为我们面向对象的设计和编码过程中不败的法则。那么什么是面向对象的基本原则呢?这就是我们将要一一讲到的问题。
单纯的讲到一个个的原则,就是那么的寥寥几句,非常的简单,但又是非常抽象的,难以理解。怎么办?
任何的理论,只要有生动的例子来讲解或证明,就能极大的帮助理解。所以我们准备从一个个的生动的例子来阐述我们的面向对象的基本原则。讲那些例子呢?上面我们说到,模式都是极大的遵从了这些原则的,那么我们把模式作为例子,来说明这些原则,不是我们信手拈来的吗?
现在我们说说其中的一个原则:对类的功能的扩展,要多用组合,少用继承
对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。其中的原因有以下几点:
第一、子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用甚至有害的父类的方法。换句话说,子类只希望继承父类的一部分方法,怎么办?
第二、实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀。
第三、 继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况。而组合却可以比继承灵活得多,可以在运行期才决定某个对象。
嗨!光说这么多一二三有什么用,我们就是想看看实际情况是不是像上面说的那样呢?还是来看看实际的例子吧!
现在我们需要这样一个HashMap,它除了能按常规的Map那样取值,如get(Object obj)。还能按位取值,像ArrayList那样,按存入对象对的先后顺序取值。
对于这样一个问题,我们首先想到的是做一个类,它继承了HashMap类,然后用一个ArrayList属性来保存存入的key,我们按key的位来取值,代码如下:
public class ListMap extends HashMap {
private List list;
public ListMap() {
         super();
          this.list = new ArrayList();
}
public Object put(Object key,Object value)
{
         if(list.contains(key))
          {
                 list.remove(key);
          }
         this.list.add(key);
          return super.put(key,value);
}
public Object getKey(int i)
{
          return this.list.get(i);
}
public Object getValue(int i)
{
          return this.get(getKey(i));
}
public int size()
{
          return this.list.size();
}
}
这个ListMap类对HashMap作了一定的扩展,很简单就实现了上面我们所要求的功能。然后我们对该类做一下测试:
ListMap map = new ListMap();
         map.put("a","111");
         map.put("v","190");
         map.put("d","132");
          for(int i=0;i<map.size();i++)
          {
                 System.out.println(map.getValue(i));
          }
测试结果为:
111
190
132
正是我们所需要看到的结果。如此说来,这个ListMap类就可以放心的使用了吗?有实现了这样功能的类,你的同事或朋友也可能把这个类拿来使用一下,他可能写出来如下的代码:
ListMap map = new ListMap();
         map.put("a","111");
         map.put("v","190");
         map.put("d","132");
         String[] list = (String[])map.values().toArray(new String[0]);
          for(int i=0;i<list.length;i++)
          {
                 System.out.println(list[i]);
          }
运行的结果如下:
132
111
190
哎哟,怎么回事啊?与上面的顺序不对了。你朋友过来找你,说你写的代码怎么不对啊?你很吃惊,说把代码给我看看。于是你看到了上面的代码。你大骂道,混蛋,怎么不是用我的getValue方法啊?你朋友搔搔头道,values方法不是一样的吗?你也没告诉我不能用啊?
通过上面的例子,我们看到了继承的第一个危害:继承不分青红皂白的把父类的公有和受保护的方法统统继承下来。如果你的子类没有对一些方法重写,就会对你的子类产生危害。上面的ListMap类,你没有重写继承自HashMap类的values方法,而该方法仍然是按HashMap的方式取值,没有先后顺序。这时候,如果在ListMap类的对象里使用该方法取得的值,就没有实现我们上面的要求。
接上面的那个例子,你听了朋友的抱怨,摇摇头,想想也是,不能怪他。你只得把values方法在ListMap类重写一遍,然后又嘀咕着,我是不是该把HashMap类的公有方法在ListMap类里全部重写?很多方法根本没有必要用到啊?……
对了,很多方法在ListMap里根本不必用到,但是你用继承的话,还不得不在ListMap里重写它们。如果用组合的话,就没有上面的烦恼了:
public class MyListMap {
private HashMap map;
private List list;
public MyListMap()
{
         this.map = new HashMap();
          this.list = new ArrayList();
}
public Object put(Object key,Object value)
{
         if(list.contains(key))
          {
                 list.remove(key);
          }
         this.list.add(key);
          return this.map.put(key,value);
}
public Object getKey(int i)
{
          return this.list.get(i);
}
public Object getValue(int i)
{
          return this.map.get(getKey(i));
}
public int size()
{
          return this.list.size();
}
}
这样,你的朋友就只能使用你的getKey和getValue方法了。如果他向你抱怨没有values方法,你尽可以满足他的要求,给他添加上那个方法,而不必担心可能还有方法没有被重写了。
我们来看Adapter模式,该模式的目的十分简单:我手里握有一些实现了WhatIHave接口的实现,可我觉得这些实现的功能不够用,我还需要从Resource类里取一些功能来为我所用。Adapter模式的解决方法如下:
public interface WhatIHave
{
          public void g();
}
public class Resource
{
          public void f()
          {
               ……
          }
          public void h()
          {
               ……
          }
}
上面是两个基础类,很明显,我们所要的类既要有g()方法,也要有f()和h()方法。
Public class WhatIWant implements WhatIHave
{
          private Resource res;
          public WhatIWant()
          {
                 res = new Resource();
}
public void g()
{
       ……
}
public void f()
{
         this.res.f();
}
public void h()
{
         this.res.h();
}
}
上面就是一个Adapter模式最简单的解决问题的思路。我们主要到,对于Resource类,该模式使用的是组合,而不是继承。这样使用是有多个原因:第一,Java不支持多重继承,如果需要使用好几个不同的Resource类,则继承解决不了问题。第二,如果Resource类还有一个方法:k(),我们在WhatIWant类里使用不上的话,继承就给我们造成多余方法的问题了。
如果说Adapter模式对组合的应用的目的十分简单明确,那么Decorator模式对组合的应用简直就是令人叫绝。
让我们还是从Decorator模式的最佳例子说起,咖啡店需要售卖各种各样的咖啡:黑咖啡、加糖、加冰、加奶、加巧克力等等。顾客要买咖啡,他可以往咖啡任意的一种或几种产品。
这个问题一提出来,我们最容易想到的是继承。比如说加糖咖啡是一种咖啡,满足ia a的句式,很明显,加糖咖啡是咖啡的一个子类。于是,我们马上可以赋之行动。对于咖啡我们做一个咖啡类:Coffee,咖啡加糖:SugarCoffee,咖啡加冰:IceCoffee,咖啡加奶:MilkCoffee,咖啡加巧克力:ChocolateCoffee,咖啡加糖加冰:SugarIceCoffee……
哎哟,我们发现问题了:这样下去我们的类好多啊。可是咖啡店的老板还不放过我们,他又逼着我们增加蒸汽咖啡、加压咖啡,结果我们发现,每增加一种新的类型,我们的类好像是成几何级数增加,我们都要疯了。
这个例子向我们展示了继承的第二个缺点,会使得我们的子类快速的膨胀下去,达到惊人的数量。
怎么办?我们的Decorator模式找到了组合来为我们解决问题。下面我们来看看Decorator模式是怎么来解决这个问题的。
首先是它们的共同接口:
package decorator;

interface Product {
public double money();
}

//咖啡类:
class Coffee implements Product {
public double money() {
    return 12;
}
}

//加糖:
class Sugar implements Product {
private Product product;

public Sugar(Product product) {
    this.product = product;
}

public double money() {
    return product.money() + 2;
}
}

//加冰:
class Ice implements Product {
private Product product;

public Ice(Product product) {
    this.product = product;
}

public double money() {
    return product.money() + 1.5;
}
}

//加奶:
class Milk implements Product {
private Product product;

public Milk(Product product) {
    this.product = product;
}

public double money() {
    return product.money() + 4.0;
}
}

//加巧克力:
class Chocolate implements Product {
private Product product;

public Chocolate(Product product) {
    this.product = product;
}

public double money() {
    return product.money() + 5.5;
}
}
public class DecoratorModel{
public static void main(String [] args){
    Product coffee = new Coffee();
    Product sugarCoffee = new Sugar(coffee);
    Product sugarmilkCoffee = new Milk(sugarCoffee);
    System.out.println("加糖咖啡:"+sugarCoffee.money());
    System.out.println("加糖加奶咖啡:"+sugarmilkCoffee.money());
}
}

我们来看客户端的调用。
如果顾客想要黑咖啡,调用如下:
Product prod = new Coffee();
System.out.println(prod.money());
如果顾客需要加冰咖啡,调用如下:
Product prod = new Ice(new Coffee());
System.out.println(prod.money());
如果顾客想要加糖加冰加奶加巧克力咖啡,调用如下:
Product prod = new Chocolate(new Milk(new Ice(new Sugar())));
System.out.println(prod.money());
通过上面的例子,我们可以看到组合的又一个很优越的好处:能够在运行期创建新的对象。如上面我们的加冰咖啡,我们没有这个类,却能通过组合在运行期创建该对象,这的确大大的增加了我们程序的灵活性。
如果咖啡店的老板再要求你增加加压咖啡,你就不会再担心了,只给他增加了一个类就解决了所有的问题。
分享到:
评论

相关推荐

    10丨理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?1

    其中,“组合优于继承”是一条广为流传的原则,意味着在设计软件时,我们应该优先考虑使用组合(Composition)而不是继承(Inheritance)。这条原则的提出主要是为了解决继承带来的潜在问题,如代码的可维护性、可...

    java设计模式选择题复习题.pdf

    面向对象设计原则包括开闭原则(对扩展开放,对修改关闭)、依赖倒转原则(依赖于抽象而非具体实现)、多用组合少用继承原则(降低耦合度)、高内聚低耦合原则等。这些原则指导我们写出更高质量的代码。 观察者模式...

    《软件秘笈:设计模式那点事》郑阿奇(编者) -电子工业出版社

    1.3.3 多用组合少用继承 1.3.4 “开-闭”原则 1.4 设计模式概述 1.4.1 什么是设计模式 1.4.2 为什么需要设计模式 1.4.3 如何正确应用设计模式 1.5 设计模式的分类 1.5.1 创建型模式 1.5.2 结构型模式 1.5.3...

    设计模式汇总(含思维导图)

    3. 多用组合少用继承:组合和聚合能增加系统的灵活性。 4. 高内聚低耦合原则:提高模块的独立性,降低模块间的耦合度。 5. 单一职责原则:一个类应该仅有一个引起它变化的原因。 6. 里氏替换原则:任何基类可以出现...

    自己总结的设计模式笔记

    合成复用原则是指要少用继承,多用合成关系来实现。合成包括组合、聚合等关系。 4. 依赖倒转原则 依赖倒转原则是指抽象不应该依赖于细节,细节应当依赖于抽象。要针对接口编程,而不是针对实现编程。 5. 接口隔离...

    设计模式笔记

    抽象工厂模式同样遵循“开-闭”原则,提倡多用对象组合,少用继承,并且针对抽象编程。 建造者模式,又称为生成器模式,它将复杂对象的构建与其表示分离,使得相同的构建过程可以创建不同的表示。在手机套餐的例子...

    Java设计模式综合应用场景

    - 多用组合,少用继承:组合让对象之间的关系更加灵活,避免了单个类过于庞大和复杂的问题。 通过这样的设计,系统能够灵活地应对订单处理中的各种变化,如新的税金政策、积分规则、销售票据格式等,同时也易于...

    行业文档-设计装置-一种多用组合式台架.zip

    标题中的“行业文档-设计装置-一种多用组合式台架”揭示了文档的主要内容,它涉及到了工业设计和工程领域中的装置设计。这个标题暗示我们,这份文档可能包含了一个创新性的设计方案,该方案着重于多功能性和组合性,...

    行业文档-设计装置-多用途组合教具.zip

    7. **资源共享**:随着数字化教育的发展,多用途组合教具的使用也能够与在线教育资源结合,形成线上线下相辅相成的教学模式。例如,配合教学视频或软件,教具的使用可以更加直观,学习效果更佳。 8. **评估与反馈**...

    设计模式综合训练报告

    **多用组合少用继承**: - 使用组合而非继承来扩展功能。例如,通过组合不同的装饰器来构建不同样式的销售票据,而不是通过继承的方式。 #### 四、源程序简单说明 在提供的部分源代码中,可以看到一个名为 `...

    java设计模式选择题复习

    - **多用组合少用继承原则**:优先考虑组合而非继承,以增强灵活性和减少耦合。 - **高内聚-低耦合原则**:每个模块都应该只负责单一的功能,减少模块间的依赖。 #### 观察者模式的推拉差异及其适用场景 - **推...

    Head.First 设计模式学习笔记.pdf

    - **多用组合,少用继承**:“多用组合,少用继承。” 继承虽然能简化代码,但也可能导致复杂的类层次结构,增加维护成本。而组合则能够更好地应对变化,提供更多的灵活性。 #### 三、具体设计模式 - **策略模式**...

    行业文档-设计装置-用于幼儿教学的多用组合台.zip

    《用于幼儿教学的多用组合台》是一款专为儿童教育设计的创新装置,它结合了多种功能,旨在提升幼儿的学习体验和教学质量。这款设计不仅考虑到了儿童的成长需求,还兼顾了教师的教学便利性,体现了现代教育科技与人性...

    行业文档-设计装置-一种多用组合教师用尺.zip

    标题中的“行业文档-设计装置-一种多用组合教师用尺”揭示了这是一份关于教育设备设计的专业文档,特别是针对教师使用的多功能尺子。这种工具可能是为了提高教学效率和便利性而创新设计的,可能集多种测量功能于一体...

    《Head_First设计模式》.pdf

    - **多用组合,少用继承**:使用组合的方式提高代码的复用性和可维护性。 ##### 1.2 实现案例 - **抽象类**: `Character` - **接口**: `WeaponBehavior` - **具体类**: `King`, `Queen`, `Troll`, `Knight` 继承 `...

    行业文档-设计装置-一种多用组合式台架构成方法.zip

    标题中的“行业文档-设计装置-一种多用组合式台架构成方法”表明这是一个关于工业设计领域的技术文档,主要探讨的是一种多功能、可组合的台架构建方式。这种设计通常涉及机械工程、结构设计和创新制造等多个方面,是...

    12种常用设计模式C#代码示例

    3、多用组合,少用继承。 4、为了交互对象之间的送耦合设计而努力。 5、类应该对扩展开发,对修改关闭。 6、依赖倒置原则:要依赖抽象,不要依赖具体类。 7、最少知识原则:只和你的密友谈话。 1,策略模式:定义...

    [设计模式]总结

    3. **多用组合,少用继承**:将变化频繁的功能封装为独立的行为类,通过组合而非继承引入到主类中,以减少代码的耦合度和提高复用性。 4. **松散耦合**:确保类之间的依赖关系最小化,有助于系统的扩展性和可维护性...

    行业文档-设计装置-多用组合式预制钢筋混凝土池.zip

    《多用组合式预制钢筋混凝土池》文档详细阐述了在建筑行业中,如何设计和应用预制钢筋混凝土池这一重要设施。这种设计装置旨在提高施工效率,降低工程成本,并确保结构的安全性和耐久性。以下是该主题的详细知识点:...

    行业文档-设计装置-组合便携多用教学板.zip

    《行业文档-设计装置-组合便携多用教学板》是一个关于创新教育设备设计的专题资料,主要聚焦在一种能够适应多种教学需求的便携式教学板。这种教学板的设计理念在于提供一个灵活、实用的教学工具,以满足现代教育环境...

Global site tag (gtag.js) - Google Analytics