`
蓝桃老K
  • 浏览: 2847 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
文章分类
社区版块
存档分类
最新评论

继承(Is-A)还是聚合(Has-a)?这是一个问题

阅读更多

昨天遇见了一个问题,有人想实现一个常用的数据结构栈(Stack),基于链表的,于是在群里贴了一段代码:

 

public class Stack extends LinkedList {
	public void push(Object object) { // 压栈
	}

	public Object peek() { // 获取栈顶元素
		return null;
	}

	public boolean isEmpty() { // 判断栈是否为空
		return false;
	}

	public Object pop() { // 弹栈
		return null;
	}
}

 

按照这种其实对应的实现代码很简单:

public class Stack extends LinkedList {
	public void push(Object object) { // 压栈
		add(0, object);
	}

	public Object peek() { // 获取栈顶元素
		if (isEmpty()) {
			return null;
		}
		return get(0);
	}

	public boolean isEmpty() { // 判断栈是否为空
		return size()==0;
	}

	public Object pop() { // 弹栈
		if (isEmpty()) {
			return null;
		}
		return remove(0);
	}
}

 

但是这样的设计合理吗?当面对继承和聚合不知道如何选择的时候,如何做出艰难的决定呢?

 

继承是一种Is-a关系,也就是说,如果当你要设计的类B在语义上可以说是A类的话,那么可以让B类继承A类,例如Cat(猫)是Animals(动物),我们就能让Cat extends Animals。而聚合是一种Has-a(包含)关系,A要设计的B类在语义上包含另外一个类,并且在AB中以整体的功能充当某个角色的时候,注意:很多时候,A类在B类外面是透明的。考虑使用聚合。例如,一只猫(Cat)有眼睛(Eyes)。那么可以设计成Cat类包含Eyes类,至于为什么A类在B类外是透明的,如下:

public class Cat {
	private Eyes eyes;
	
	public Cat(Eyes eyes) {
		this.eyes = eyes;
	}

	public void see() {
		eyes.see();
	}
}

 

public class Eyes {
	public void see() {
		System.out.println("see");
	}
}

  

 

在使用Cat类对象时,并不需要关注其内部对于see()方法的实现,很明显的猫也不是眼睛(不是Is-a关系),再来看前面的问题,栈并不是一个链表,所以采用继承的方式是不合适的,继承之所以是Is-a关系,是因为它继承了父类全部的方法,即它拥有了父类所有的行为。在上面的继承实现栈的方法中,栈继承了链表的所有方法,这样你就不能保证栈的特性—first in last out(先进后出)了。因为它同时也是链表,可以调用其从父类继承的方法在任一位置做删除和插入。下面是一种更好的has-a实现方式:

 

public class Stack {
	private List list;
	
	public void push(Object object) { // 压栈
		list.add(0, object);
	}

	public Object peek() { // 获取栈顶元素
		if (isEmpty()) {
			return null;
		}
		return list.get(0);
	}

	public boolean isEmpty() { // 判断栈是否为空
		return list.size()==0;
	}

	public Object pop() { // 弹栈
		if (isEmpty()) {
			return null;
		}
		return list.remove(0);
	}
}

 

这个时候栈对外的表现就纯粹的是栈了,因为它只具有栈的行为。至于里面是如何实现的,用户无需关心,同时也是安全的,用户除了入栈出栈之外,无法做其他的删除插入操作,这里选用了List作为其属性,没有限制用户使用ArrayList或者LinkedList,其实可以使用Collection属性,用户的选择就更大了。当然也能使用数组。可见对外表现一致的栈,其内部实现可以多样化,即在has-a关系中A类在B类外面是透明的. 在利用List实现时你也可以采用与上面不同的策略:每次取出列表的最后一个元素,将元素入栈时加入列表的最后一个位置。

现在你可以做出艰难的决定了。简单地说,如果在语义上你能说BA,那么采用继承,否则使用聚合。聚合使用的场合会多一些。

 

继承也能是has-a,聚合也能使Is-a

如果B类继承了A类,毫无疑问的,B类具有了A类的所有行为,在逻辑上B类其实Has A了。其中super关键字就表示了一个类的父类对象引用,可见说继承是一种has-a的关系逻辑上是没有错误的。为什么继承不能完全的替代has-a关系呢?这是因为继承时,继承需要行为的时候将不需要的行为,甚至是不能出现的行为,如上文链表的任意位置的插入删除行为也继承了下来。java只有一种继承方式,这源自java的设计哲学“C++ --”。在C++中有更多样的继承方式,其中的一种继承就是一种完全的has-a关系而非Is-a关系----私有继承。B类私有继承自A类则将A类中的所有行为全部继承为B类自己的私有行为,这样B类对外再也不能表现出A类的行为,是一种完全的has-a关系。

当然has-a关系也能体现出Is-a的关系,这来自用户的设计,可以在B类中定义所有A类的方法,然后内部调用A类实例去实现,这样B类就能对外表现A类的所有行为了。当然拥有A的同名方法未必能对外表现为B。除非你同时继承A,等等……是不是有点乱了。

 

B类继承A类,B类还包含A类的实例(A a = new A())。这样在B中不是有两个A的实例了一个是super一个是a。确实是这样。但是如果A是一个接口呢?

B实现了接口A,然后B类中中还有一个实现A的类的实例:

 

这样B对外可以作为A使用,而对内调用a的方法来进行实际的操作,B好像什么也没有做啊?不是什么也没有做,B做了代理,这就是我们常说的代理模式。

 (未完不知道续不续)

分享到:
评论

相关推荐

    C++给学生习题第8章练习题.pdf

    1. has-a关系表示一个类包含或拥有另一个类的实例作为其成员,这种关系也被称为聚合。例如,一个汽车类可能has-a引擎类,意味着汽车包含一个引擎对象。 2. uses-a关系则表示一个类使用另一个类的服务或功能,但并不...

    架构师之路4-设计模式.pdf

    UML类图的标记语言我都忘了我记了多少遍,忘了多少遍了!只有捋出来头绪和关联,才可能永远记住并融入你的思维方式,没有任何联系的东西,我们...has-a:包括四种关联关系的,组合,聚合,关联和依赖,依据关系强弱排名

    组合和继承的联合

    这种关系可以理解为“has-a”关系,即一个类包含另一个类的实例作为其成员变量。例如,在设计车辆类时,我们可以有一个Engine类,然后在Car类中包含Engine的实例,表示汽车“拥有”引擎。组合具有灵活性,因为内部类...

    2022年Decorator模式中遭遇继承与聚合Java教程.docx

    继承是一种“is-a”关系,而组合则是一种“has-a”关系。在Decorator模式中,子类(Decorator)继承父类(Component),同时持有一个父类对象的引用,这就是继承与组合的结合。这种方式允许我们在运行时动态地给对象...

    编程思想14章继承和组合的介绍

    组合,又称为聚合,是一种“has-a”关系,表示一个对象可以包含其他对象作为其成员。相比于继承的“is-a”关系,组合更加灵活,因为它强调的是对象之间的动态关系,而不是静态的类层次结构。在C++中,我们可以直接将...

    合成聚合复用原则_刘邦VS韩信.pdf

    聚合关系类似于“has-a”关系,它表示一种整体与部分的关系,但并不意味着整体的生命周期管理包含部分。例如,一个班级可以聚合多个学生,班级对象持有学生的引用,但学生可以属于多个班级,他们的生命周期独立于...

    SCJP各章要点02

    在探讨SCJP(Sun Certified Java Programmer)认证的核心概念时,第二章深入解析了面向对象编程(OOP)的基石——封装、继承(IS-A)、关联(HAS-A),以及多态性,这些概念对理解Java语言的复杂性和灵活性至关重要。...

    C++程序设计教程:第十章 继承.ppt

    继承侧重于“is-a”关系,而组合(聚合)则强调“has-a”关系。组合允许一个类包含另一个类的实例作为其成员,提供更大的灵活性和封装性。 6. 多继承概念(Multi-Inheritance Concept) 多继承是指一个派生类可以...

    面向对象的三个基本特征是:封装、继承、多态。

    聚合则是一种表示整体与部分关系的方式,它体现的是“has-a”的关系,例如一个头(Head)包含眼睛(Eye)、鼻子(Nose)、嘴巴(Mouth)等。聚合关系告诉我们类是如何组合的,它强调的是部分和整体之间的关系,而...

    c讲稿第5章继承与派生.pptx

    组合是“has-a”关系,一个类包含另一个类的对象。组合比继承更灵活,能更好地封装和解耦。 总的来说,理解和掌握继承与派生是深入学习C++面向对象编程的关键,它有助于设计出更加模块化、可维护的代码。在实际编程...

    2009系统分析与设计期末考卷(A卷)1

    其他选项中,泛化(Generalization)表示继承关系,依赖(Dependence)表示一个类依赖于另一个类,继承(Inheritance)表示类之间的is-a关系。 5. 哪种敏捷方法属于极限编程(Extreme Programming, XP)? 极限...

    设计模式1

    组合和聚合强调“has-a”关系,而继承强调“is-a”关系。前者能更好地封装组件,减少组件间的耦合,增强系统的灵活性。 UML类图是描述这些设计原则和模式的有效工具,其中: - 泛化关系表示继承,用空心箭头表示,...

    Java软件开发实战 Java基础与案例开发详解 7-1 面向对象的分析与设计简介 共11页.pdf

    - **继承**:一个类继承自另一个类,表示“is-a”关系。 - **关联**:两个或多个类之间的简单连接,表示“has-a”关系。 - **聚合**:一种特殊的关联,表示“has-a”关系,但聚合关系中的对象可以独立存在。 - **...

    面向对象大智慧

    - **聚合**:表示一种“has-a”关系,比如汽车有一个引擎。 **继承的局限**:虽然继承可以极大地提高代码的重用性,但过度使用继承可能会导致类层次结构过于复杂,增加系统的维护成本。 **结论**:继承是面向对象...

    2018-autumn-hw-17020031057-石晓晨-201810071

    一个类可以继承自另一个类("is-a"关系),或者一个类可以实现一个接口,甚至接口可以继承其他接口。这种关系使得子类可以继承父类的属性和方法,或实现特定的功能。 接下来,作业中的编程题主要考察了基本的Java...

    Java中的30个概念

    - **类之间的关系**:依赖(use-a)、聚合(has-a)和继承(is-a)是类之间常见的关系,继承体现了“is-a”关系。 - **构造器**:构造器用于创建和初始化新对象,每个类都有一个与之同名的构造方法。 这些概念...

    springboot项目知识.docx

    - "has-a"表示聚合关系,表示一个对象包含另一个对象,比如`Person`对象有`Eye`对象,这里的`Person`和`Eye`是聚合关系,也可以理解为接口或依赖关系。 5. **MyBatis的Mapper文件**: MyBatis是一个优秀的持久层...

    c++经典问题(初学者的经典内容)

    - **`has-a`**:表示聚合关系,意味着一个类包含另一个类的对象作为其成员变量。 **关键点:** - **继承**:通常用于实现多态性,表示“is-a”关系。 - **聚合**:表示“has-a”关系,强调的是组合而非继承。 ####...

    自考软件工程简答小抄.pdf

    "has-a"表示组合或聚合关系,而"is-a"指的是继承关系。 10. PDL(程序设计语言):这是一种用于描述算法的伪代码,通常用在软件开发的早期阶段,帮助理解程序的逻辑结构。 由于文档内容可能由于OCR扫描技术导致的...

Global site tag (gtag.js) - Google Analytics