java 多用组合_java 为什么说多用组合,少用继承?
对类的功能的扩展,要多用组合,少用继承。
组合:新的类由现有对象所组成。
继承:按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新代码。
当继承现有类型时,也就创造新的类型,这个新类型不仅包括现有类型的所有成员(尽管private成员被隐藏起来并且不可访问,),更重要的是它复制了基类的接口,也就是说所有可以发送给基类对象(父类)的消息同时也可以发送给导出(子类)对象。由于通过发送给类的消息类型可知类的类型,所以这也就意味着导出类与基类有相同的类型。
对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。其中的原因有以下几点:
第一、子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用甚至有害的父类的方法。换句话说,子类只希望继承父类的一部分方法,怎么办?
第二、实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀。
第三、 继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况。而组合却可以比继承灵活得多,可以在运行期才决定某个对象。
嗨!光说这么多一二三有什么用,我们就是想看看实际情况是不是像上面说的那样呢?还是来看看实际的例子吧!
现在我们需要这样一个HashMap,它除了能按常规的Map那样取值,如get(Object obj)。还能按位取值,像ArrayList那样,按存入对象对的先后顺序取值。
对于这样一个问题,我们首先想到的是做一个类,它继承了HashMap类,然后用一个ArrayList属性来保存存入的key,我们按key的位来取值,代码如下:
Java代码
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
{
System.out.println(map.getValue(i));
}
测试结果为:
111
190
132
正是我们所需要看到的结果。如此说来,这个ListMap类就可以放心的使用了吗?有实现了这样功能的类,你的同事或朋友也可能把这个类拿来使用一下,他可能写出来如下的代码:
Java代码
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
{
System.out.println(list[i]);
}
运行的结果如下:
132
111
190
哎哟,怎么回事啊?与上面的顺序不对了。你朋友过来找你,说你写的代码怎么不对啊?你很吃惊,说把代码给我看看。于是你看到了上面的代码。你大骂道,混蛋,怎么不是用我的getValue方法啊?你朋友搔搔头道,values方法不是一样的吗?你也没告诉我不能用啊?
通过上面的例子,我们看到了继承的第一个危害:继承不分青红皂白的把父类的公有和受保护的方法统统继承下来。如果你的子类没有对一些方法重写,就会对你的子类产生危害。上面的ListMap类,你没有重写继承自HashMap类的values方法,而该方法仍然是按HashMap的方式取值,没有先后顺序。这时候,如果在ListMap类的对象里使用该方法取得的值,就没有实现我们上面的要求。
接上面的那个例子,你听了朋友的抱怨,摇摇头,想想也是,不能怪他。你只得把values方法在ListMap类重写一遍,然后又嘀咕着,我是不是该把HashMap类的公有方法在ListMap类里全部重写?很多方法根本没有必要用到啊?……
对了,很多方法在ListMap里根本不必用到,但是你用继承的话,还不得不在ListMap里重写它们。如果用组合的话,就没有上面的烦恼了:
Java代码
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模式的解决方法如下:
Java代码
public interface WhatIHave
{
public void g();
}
public class Resource
{
public void f()
{
……
}
public void h()
{
……
}
}
上面是两个基础类,很明显,我们所要的类既要有g()方法,也要有f()和h()方法。
Java代码
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模式是怎么来解决这个问题的。
首先是它们的共同接口:
Java代码
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());
通过上面的例子,我们可以看到组合的又一个很优越的好处:能够在运行期创建新的对象。如上面我们的加冰咖啡,我们没有这个类,却能通过组合在运行期创建该对象,这的确大大的增加了我们程序的灵活性。
如果咖啡店的老板再要求你增加加压咖啡,你就不会再担心了,只给他增加了一个类就解决了所有的问题。
关于组合和继承的总结:
组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承则是隐式地这样做。
继承是"is-a"关系
组合是"has-a"关系
到底该使用组合还是继承:问一问自己是否需要从新类向基类进行向上转型,若必要则继承是必要的,反之需要思考。
1)组合(has-a)关系可以显式地获得被包含类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包含类和父类对应,而组合外部类和子类对应。
2)组合关系在运行期决定,而继承关系在编译期就已经决定了。
3)组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。
4)当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。
5)最重要的一点,使用继承关系时,可以实现类型的回溯,即用父类变量引用子类对象,这样便可以实现多态,而组合没有这个特性。
6)还有一点需要注意,如果你确定复用另外一个类的方法永远不需要改变时,应该使用组合,因为组合只是简单地复用被包含类的接口,而继承除了复用父类的接口外,它甚至还可以覆盖这些接口,修改父类接口的默认实现,这个特性是组合所不具有的。
7)从逻辑上看,组合最主要地体现的是一种整体和部分的思想,例如在电脑类是由内存类,CPU类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。
8)这两者的区别主要体现在类的抽象阶段,在分析类之间的关系时就应该确定是采用组合还是采用继承。
9)引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活"。
10)组合技术通常是用于在新类中使用现有类的功能而非它的接口的情形。
java 多用组合_java 为什么说多用组合,少用继承?相关推荐
- java 排列组合_java 高效率的排列组合算法(java实现)
package BeanUtil; import java.util.ArrayList; import java.util.List; import com.work.core.exception. ...
- java显示字母数字组合_Java字母加数字组合比较大小
针对字符串是数字和字母结合而进行的,如"a20"和"a9";比较而得出结果是"a20">"a9".这种情况直接调用 ...
- 【Java设计模式 面向对象设计思想】五 多用组合少用继承编程
我们经常会听到说多用组合少用继承,但是拜托,继承是面向对象四大特性之一啊,为什么地位反不如组合了呢,为什么不推荐使用继承?组合相比继承有哪些优势?如何判断该用组合还是继承?围绕这三个问题进行以下讨论 ...
- 为何说要多用组合少用继承?
在面向对象编程中,有一条非常经典的设计原则,那就是:组合优于继承,多用组合少用继承.为什么不推荐使用继承?组合相比继承有哪些优势?如何判断该用组合还是继承?今天,我们就围绕着这三个问题,来详细讲解一下 ...
- 为什么要多用组合少用继承?
面向对象编程时,有十条很重要的原则: 代码复用 封装变化 开闭原则 单一职责原则 依赖注入/依赖倒置原则 里氏替换原则(LSP) 接口隔离原则(ISP) 多用组合,少用继承 面向接口编程 委托原则 上 ...
- 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
在面向对象编程中,有一条非常经典的设计原则,那就是:组合优于继承,多用组合少用继承.为什么不推荐使用继承?组合相比继承有哪些优势?如何判断该用组合还是继承?今天,我们就围绕着这三个问题,来详细讲解一下 ...
- java 字符串排列组合_Java 程序计算列出字符串的所有排列组合
Java 程序计算列出字符串的所有排列组合 在此示例中,我们将学习计算Java中字符串的所有排列组合. 要理解此示例,您应该了解以下Java编程主题: 字符串的排列是指可以通过互换字符串字符的位置来形 ...
- java设计模式 组合_JAVA 设计模式 组合模式
用途组合模式 (Component) 将对象组合成树形结构以表示"部分-整体"的层次结构. 组合模式使得用户对单个对象和组合对象的使用具有唯一性. 组合模式是一种结构型模式. 结构 ...
- JAVA练习177-有重复字符串的排列组合
有重复字符串的排列组合.编写一种方法,计算某字符串的所有排列组合. 示例1: 输入:S = "qqe" 输出:["eqq","qeq", ...
- java列出所有组合_关于Java中列出一组数据的所有组合算法
最近没有项目,顺序做了一个算法. 题目:列出一组数据所有的组合内容.如一组数据:12345,则列出这组数据所有组合.组合项可以有一项,二项,三项......如合法项为:1,12,123,1234,12 ...
最新文章
- 2017-2-23 C#基础 中间变量
- JQuery用户名无刷新验证
- dlib 68个关键点 人脸姿态
- java线程主要状态及转换_Java线程状态转换及控制
- java打jar包,引用其他.jar文件
- 如何从官方渠道下载Spring MVC所需jar包
- 基于.NetCore3.1系列 —— 日志记录之初识Serilog
- python爬取网易云音乐问题陈述_python 网易云音乐 评论爬取问题
- php swoole 心跳,聊聊swoole的心跳
- DAS 2020 Keynote Speech | 深度学习时代的 OCR
- 国外自由车流ETC电子收费系统集成技术发展状况
- 批量输出lib文件名(PCL或者opencv等环境配置)
- tcpip详解卷一 pdf_清华大牛爆肝分享网络底层/网络协议/TCP/IP协议详解卷一
- ActivityMq的使用(小例子)
- tableau 日周月筛选器_【数据可视化】Tableau教程(六)日历热力图
- 面试智力题,1000瓶水,其中一瓶有毒,而且毒性无敌,稀释一亿倍毒性都不减,毒性的发作时间最长为1小时,请问怎样可以在两个小时之内找出哪瓶水有毒
- 1月16日服务器例行维护更新公告,1月16日例行维护暨版本更新公告
- 海康威视接口在线调试
- oracle 12.2R2 安装GI跑root.sh遇到CLSRSC-400
- latex 公式编号的自定义
热门文章
- Maya 基础教程 、 基础操作讲解
- 软件测试自学还是培训?
- 国产麒麟操作系统kylin V10 sp2操作系统安装openldap和kerberos
- 285个地级市的灯光数据(1992-2013年)和雾霾(PM2.5)数据(1998-2016年)
- 冲刺大厂每日算法面试题,动态规划21天——第七天
- php脉聊交友源码_脉聊源码-PHP脉聊交友网站源码(附app源码)下载-西西软件下载...
- idea 一直不停的updating index
- if (resultCode == RESULT_OK) 在红米手机上resultCode返回并不是RESULT_OK
- 通俗地讲一下庞加莱猜想是怎么回事(from 鼓浪)
- 苏宁易购,淘宝网,京东商城,百万级价格数据海量抓取