刚刚接触模式或者学习模式的人,经常会有这样的问题,为什么模式是成功的呢?很多人都会说模式是经验的积累,当然是正确的。可是经验为什么偏偏就证明了这种模式是正确的呢?这其中起用作的就是面向对象的基本原则。正是因为模式都或多或少的符合了面向对象的基本原则,所以模式才成为我们面向对象的设计和编码过程中不败的法则。那么什么是面向对象的基本原则呢?
任何的理论,只要有生动的例子来讲解或证明,就能极大的帮助理解。所以我们准备从一个个的生动的例子来阐述我们的面向对象的基本原则。讲那些例子呢?上面我们说到,模式都是极大的遵从了这些原则的,那么我们把模式作为例子,来说明这些原则,不是我们信手拈来的吗?
现在我们说说其中的一个原则:对类的功能的扩展,要多用组合,少用继承。
对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。其中的原因有以下几点:
  • 子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用甚至有害的父类的方法。换句话说,子类只希望继承父类的一部分方法,怎么办?
  • 实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀。
  • 继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况。而组合却可以比继承灵活得多,可以在运行期才决定某个对象。
比如说,现在我们需要这样一个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类就可以放心的使用了吗?实现了这样功能的类,你的同事或朋友也可能把这个类拿来使用一下,他可能写出来如下的代码:

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]);}
哎哟,怎么回事啊?与上面的顺序不对了。你朋友过来找你,说你写的代码怎么不对啊?你很吃惊,说把代码给我看看。于是你看到了上面的代码。你大骂道,混蛋,怎么不是用我的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模式是怎么来解决这个问题的。
首先是它们的共同接口:
public interface Product{
public double money();
}
咖啡类:
public class Coffee implements Product{public double money() {return 12;}
}
加糖:
public class Sugar implements Product{private Product product;public Sugar(Product product) {this.product = product;}public double money(){return product.money+2;}
}
加冰:
public class Ice implements Product{private Product product;public Ice(Product product) {this.product = product;}public double money(){return product.money+1.5;}
}
加奶:
public class Milk implements Product{private Product product;public Milk(Product product) {this.product = product;}public double money(){return product.money+4.0;}}
加巧克力:
public class Chocolate implements Product{private Product product;public Chocolate(Product product) {this.product = product;}public double money(){return product.money+5.5;}
}

我们来看客户端的调用。
如果顾客想要黑咖啡,调用如下:
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());
通过上面的例子,我们可以看到组合的又一个很优越的好处:能够在运行期创建新的对象。如上面我们的加冰咖啡,我们没有这个类,却能通过组合在运行期创建该对象,这的确大大的增加了我们程序的灵活性。
如果咖啡店的老板再要求你增加加压咖啡,你就不会再担心了,只给他增加了一个类就解决了所有的问题。

个人理解设计为何要多用组合,少用继承相关推荐

  1. 【Java设计模式 面向对象设计思想】五 多用组合少用继承编程

    我们经常会听到说多用组合少用继承,但是拜托,继承是面向对象四大特性之一啊,为什么地位反不如组合了呢,为什么不推荐使用继承?组合相比继承有哪些优势?如何判断该用组合还是继承?围绕这三个问题进行以下讨论 ...

  2. 为何说要多用组合少用继承?

    在面向对象编程中,有一条非常经典的设计原则,那就是:组合优于继承,多用组合少用继承.为什么不推荐使用继承?组合相比继承有哪些优势?如何判断该用组合还是继承?今天,我们就围绕着这三个问题,来详细讲解一下 ...

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

    在面向对象编程中,有一条非常经典的设计原则,那就是:组合优于继承,多用组合少用继承.为什么不推荐使用继承?组合相比继承有哪些优势?如何判断该用组合还是继承?今天,我们就围绕着这三个问题,来详细讲解一下 ...

  4. 为什么要多用组合少用继承?

    面向对象编程时,有十条很重要的原则: 代码复用 封装变化 开闭原则 单一职责原则 依赖注入/依赖倒置原则 里氏替换原则(LSP) 接口隔离原则(ISP) 多用组合,少用继承 面向接口编程 委托原则 上 ...

  5. 在设计原则中,为什么反复强调组合要优于继承?

    今日推荐21 款 yyds 的 IDEA插件这 56 个代码注释让我笑吐了注解+反射优雅的实现Excel导入导出(通用版)Fluent Mybatis 牛逼!Nginx 常用配置清单这玩意比Threa ...

  6. 从认知角度去理解设计

    设计并不是一味只求美感或者感觉,设计同样是一门建立在多学科基础上的科学,从认知角度来理解设计能帮助我们设计出更多尊重用户的作品,这样的设计才能经得起时间的考验,让更多用户所喜爱. 下面是我对<认 ...

  7. 【总结记录】面向对象设计OOP三大特性(封装、继承、多态)、七大基本原则的理解(结合代码、现实实例理解)

    文章目录 一. 三大特征的理解 (1)封装 (2)继承 (3)多态 (4)面向对象.面向过程的对比 二. 七大基本原则的理解 (1)单一职责原则 (2)开放封闭原则(OOP 核心) (3)里氏替换原则 ...

  8. 交互设计实用指南系列11-减少记忆负担

    http://ued.taobao.org/blog/2010/03/交互设计实用指南系列11-减少记忆负担/ 科普 <辞海>中"记忆"的定义是:"人脑对经验 ...

  9. 面向对象设计原则——优先使用对象组合,而不是继承(组合以及与继承的区别)

    看到面向对象设计原则中的合成复用原则: 优先使用对象组合,而不是继承 类继承:也叫白箱复用 对象组合:也叫黑箱复用. 继承某种程度上破坏了封装性,子父类之间的耦合性过高. 对象组合只要求被组合的对象具 ...

  10. 程矢Axure夜话:程序员眼中的原型设计视频教程之书到用时方恨少

    程矢Axure夜话:程序员眼中的原型设计视频教程之书到用时方恨少 案例描述:书到用时方恨少 这是一个经典的案例,作者曾经在培训机构工作期间发现很多的学员都很喜欢这样的课程 知识点: 动态面板的移动 动 ...

最新文章

  1. 请你描述一下 cookies,sessionStorage 和 localStorage 的区别?
  2. Java API帮助文档怎么查找?
  3. 卷积神经网络迁移学习
  4. 全局路径规划:图搜索算法介绍2(A star)
  5. vuex页面数据丢失_解决 vuex 中的数据在页面刷新之后就丢失的问题
  6. 设计模式之单例模式8种实现方式,其八:枚举方式
  7. 向前的快捷键_平面设计基础知识:平面设计师应该知道的快捷键。
  8. Leetcode 279 完美平方数
  9. Java~基于fluent-hc快速构建Http请求,结合failsafe实现异常重试
  10. 10个互联网兼职平台,让你的一技之长变现,副业薪资比日常搬砖高也太爽了
  11. C++语句 与简单方法
  12. Java程序语言(基础篇)第2章 基本程序设计 编程练习题解答
  13. nihao, woshi mr jhon
  14. 使用 @mention 功能构建 React 评论表单
  15. android平台下OpenGL ES 3.0绘制圆点、直线和三角形
  16. 京东12年被裁,昨天赔偿到账了,加调休和年假总共47万多,感谢公司!
  17. 二叉树先序遍历(递归+迭代)——java
  18. [OC学习笔记]块与大中枢开发
  19. 伯努利分布、二项分布、泊松分布、指数分布简介
  20. 【Java】多态的理解与使用

热门文章

  1. vc语言c1083错误,VC Fatal Error C1083的几种解决方案
  2. 使用ireport创建报表模板时,向subdataset中传参
  3. 计算机网络在信息时代的作用
  4. 『一篇老文章』现有p2p协议分类与简介
  5. oracle 数据库不用了,改用SQL,要学SQL了,第一个手工写的存储过程
  6. 手把手教你怎样用U盘装系统
  7. oracle加减乘除怎么写,加减乘除怎么写-加减乘除的名称怎么写-加减乘除的笔画怎么写...
  8. java程序员 thinkpad_JAVA程序员笔记本电脑推荐?
  9. 淘宝商品评论获取评论
  10. Echarts直角坐标系x轴y轴属性设置大全