昨天遇见了一个问题,有人想实现一个常用的数据结构栈(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类在语义上包含另外一个类,并且在A在B中以整体的功能充当某个角色的时候,注意:很多时候,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实现时你也可以采用与上面不同的策略:每次取出列表的最后一个元素,将元素入栈时加入列表的最后一个位置。
现在你可以做出艰难的决定了。简单地说,如果在语义上你能说B是A,那么采用继承,否则使用聚合。聚合使用的场合会多一些。
继承也能是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做了代理,这就是我们常说的代理模式。
(未完不知道续不续)
分享到:
相关推荐
1. has-a关系表示一个类包含或拥有另一个类的实例作为其成员,这种关系也被称为聚合。例如,一个汽车类可能has-a引擎类,意味着汽车包含一个引擎对象。 2. uses-a关系则表示一个类使用另一个类的服务或功能,但并不...
UML类图的标记语言我都忘了我记了多少遍,忘了多少遍了!只有捋出来头绪和关联,才可能永远记住并融入你的思维方式,没有任何联系的东西,我们...has-a:包括四种关联关系的,组合,聚合,关联和依赖,依据关系强弱排名
这种关系可以理解为“has-a”关系,即一个类包含另一个类的实例作为其成员变量。例如,在设计车辆类时,我们可以有一个Engine类,然后在Car类中包含Engine的实例,表示汽车“拥有”引擎。组合具有灵活性,因为内部类...
继承是一种“is-a”关系,而组合则是一种“has-a”关系。在Decorator模式中,子类(Decorator)继承父类(Component),同时持有一个父类对象的引用,这就是继承与组合的结合。这种方式允许我们在运行时动态地给对象...
组合,又称为聚合,是一种“has-a”关系,表示一个对象可以包含其他对象作为其成员。相比于继承的“is-a”关系,组合更加灵活,因为它强调的是对象之间的动态关系,而不是静态的类层次结构。在C++中,我们可以直接将...
聚合关系类似于“has-a”关系,它表示一种整体与部分的关系,但并不意味着整体的生命周期管理包含部分。例如,一个班级可以聚合多个学生,班级对象持有学生的引用,但学生可以属于多个班级,他们的生命周期独立于...
在探讨SCJP(Sun Certified Java Programmer)认证的核心概念时,第二章深入解析了面向对象编程(OOP)的基石——封装、继承(IS-A)、关联(HAS-A),以及多态性,这些概念对理解Java语言的复杂性和灵活性至关重要。...
继承侧重于“is-a”关系,而组合(聚合)则强调“has-a”关系。组合允许一个类包含另一个类的实例作为其成员,提供更大的灵活性和封装性。 6. 多继承概念(Multi-Inheritance Concept) 多继承是指一个派生类可以...
聚合则是一种表示整体与部分关系的方式,它体现的是“has-a”的关系,例如一个头(Head)包含眼睛(Eye)、鼻子(Nose)、嘴巴(Mouth)等。聚合关系告诉我们类是如何组合的,它强调的是部分和整体之间的关系,而...
组合是“has-a”关系,一个类包含另一个类的对象。组合比继承更灵活,能更好地封装和解耦。 总的来说,理解和掌握继承与派生是深入学习C++面向对象编程的关键,它有助于设计出更加模块化、可维护的代码。在实际编程...
其他选项中,泛化(Generalization)表示继承关系,依赖(Dependence)表示一个类依赖于另一个类,继承(Inheritance)表示类之间的is-a关系。 5. 哪种敏捷方法属于极限编程(Extreme Programming, XP)? 极限...
组合和聚合强调“has-a”关系,而继承强调“is-a”关系。前者能更好地封装组件,减少组件间的耦合,增强系统的灵活性。 UML类图是描述这些设计原则和模式的有效工具,其中: - 泛化关系表示继承,用空心箭头表示,...
- **继承**:一个类继承自另一个类,表示“is-a”关系。 - **关联**:两个或多个类之间的简单连接,表示“has-a”关系。 - **聚合**:一种特殊的关联,表示“has-a”关系,但聚合关系中的对象可以独立存在。 - **...
- **聚合**:表示一种“has-a”关系,比如汽车有一个引擎。 **继承的局限**:虽然继承可以极大地提高代码的重用性,但过度使用继承可能会导致类层次结构过于复杂,增加系统的维护成本。 **结论**:继承是面向对象...
一个类可以继承自另一个类("is-a"关系),或者一个类可以实现一个接口,甚至接口可以继承其他接口。这种关系使得子类可以继承父类的属性和方法,或实现特定的功能。 接下来,作业中的编程题主要考察了基本的Java...
- **类之间的关系**:依赖(use-a)、聚合(has-a)和继承(is-a)是类之间常见的关系,继承体现了“is-a”关系。 - **构造器**:构造器用于创建和初始化新对象,每个类都有一个与之同名的构造方法。 这些概念...
- "has-a"表示聚合关系,表示一个对象包含另一个对象,比如`Person`对象有`Eye`对象,这里的`Person`和`Eye`是聚合关系,也可以理解为接口或依赖关系。 5. **MyBatis的Mapper文件**: MyBatis是一个优秀的持久层...
- **`has-a`**:表示聚合关系,意味着一个类包含另一个类的对象作为其成员变量。 **关键点:** - **继承**:通常用于实现多态性,表示“is-a”关系。 - **聚合**:表示“has-a”关系,强调的是组合而非继承。 ####...
"has-a"表示组合或聚合关系,而"is-a"指的是继承关系。 10. PDL(程序设计语言):这是一种用于描述算法的伪代码,通常用在软件开发的早期阶段,帮助理解程序的逻辑结构。 由于文档内容可能由于OCR扫描技术导致的...