【软件重构】【代码坏味道】详解滥用类的继承体系
【摘要】 一、定义:子类应该继承超类的函数和数据,但有时子类却不想使用或者不需要继承超类的部分函数及数据,只想使用部分功能和字段。出现了不同的子类,容易存在混乱,滥用的情况。这就是继承的两面性。二、问题影响:1)由于超类基本都是抽象的,包含了没有具体实现的接口,而子类又不想实现这些接口,这就违背了继承原则2)子类只使用超类的部分功能和字段,那么他们之间的继承体系设计就存在不合理。3)如果存在这种坏味道...
一、定义:
子类应该继承超类的函数和数据,但有时子类却不想使用或者不需要继承超类的部分函数及数据,只想使用部分功能和字段。出现了不同的子类,容易存在混乱,滥用的情况。这就是继承的两面性。
二、问题影响:
1)由于超类基本都是抽象的,包含了没有具体实现的接口,而子类又不想实现这些接口,这就违背了继承原则
2)子类只使用超类的部分功能和字段,那么他们之间的继承体系设计就存在不合理。
3)如果存在这种坏味道,那么就意味着,子类或者父类会相互受到影响,很显然违反了LSP(里氏替换原则):子类必须能够替换掉他们的父类。即父类出现的地方就可以使用子类来代替,而且不会出现任何错误或者异常,常见的影响有:
- 一个子类继承了父类但是子类中的某个方法抛出了异常,而父类中该方法并没有抛出异常。
- 一个子类继承了父类,但是子类修改了某个方法的内部行为。
- 调用者只能通过子类而不能通过父类来访问类。
- 无意义的继承,子类并不是父类的一个实例。
(3)如何解决继承体系的问题
- 创建兄弟类。为子类创建兄弟类,使用函数下移或者字段下移,将所有用不到的函数和字段推给兄弟类。超类只留公共的函数
- 函数/字段下移:就是将超类中的函数/字段复制声明到每一个需要测函数的子类中,不需要的子类则不用复制声明,然后删除超类中的该函数/字段
- 修改子类。如果子类确实不需要实现超类的接口,并且超类函数也不能移动,那么子类想复用超类的部分功能,只能使用委托类取代子类或者超类,将子类与超类划清界限。
- 以委托(组合)取代子类/超类。如果两个超类与多个子类存在非常紧密的关系,修改超类都很可能破坏所有子类。可以使用委托解决这个问题,对应不同的变化原因,可以委托给不同的类,委托是对象之间常规的关系,与继承关系相比,使用委托关系时接口更清晰,耦合更少,委托即是组合,因此存在一个原则:对象组合由于继承;但是还是先建议使用继承,如果继承遇到这种问题后,才考虑使用委托重构。
看一个具体的例子:
import java.time.DayOfWeek;
import java.time.LocalDate;
public class refuseSmall {
/*
* 需求:火车票与优惠活动的业务需求
* 1.优惠活动:优惠时间,主题、基础价格
* 2.普通票:优惠时间是否过期、获取票价、退款
* 3.VIP票:优惠时间是否过期,获取票价,是否存在额外的活动
* */
}
// 优惠活动类
class Activity {
private final ActivityType type;//优惠主题
private final LocalDate date;//优惠时间
private final int price;//基础价格
public Activity(ActivityType type, LocalDate date, int price) {
this.type = type;
this.date = date;
this.price = price;
}
public LocalDate getDate() {
return date;
}
public int getPrice() {
return price;
}
public ActivityType getType() {
return type;
}
enum ActivityType {WORKSHOP, TDD, SESSION}
}
// 普通票类
class Ticket {
public final Activity activity;
public Ticket(Activity activity) {
this.activity = activity;
}
// 是否过期
public boolean isSession() {
return Activity.ActivityType.SESSION.equals(activity.getType()) && isWorkday();
}
//是否是工作时间
private boolean isWorkday() {
return !activity.getDate().getDayOfWeek().equals(DayOfWeek.SATURDAY)
&& !activity.getDate().getDayOfWeek().equals(DayOfWeek.SUNDAY);
}
//获取基础价格
public int getPrice() {
return DayOfWeek.FRIDAY.equals(activity.getDate().getDayOfWeek())
? activity.getPrice() * 2
: activity.getPrice();
}
//退款
public int refund() {
return getPrice();
}
}
// vip票
class VIPTicket extends Ticket {
private final boolean supportExtensionalActivities;
public VIPTicket(Activity activity, boolean supportExtensionalActivities) {
super(activity);
this.supportExtensionalActivities = supportExtensionalActivities;
}
public boolean isSession() {
return Activity.ActivityType.SESSION.equals(activity.getType());
}
public int getPrice() {
return super.getPrice() + 100;
}
// 查询是够存在额外的优惠活动
public boolean hasExtensionalActivities() {
return Activity.ActivityType.TDD.equals(activity.getType()) || supportExtensionalActivities;
}
}
存在的问题:
- getPrice()方法不但覆写父类的方法并且并且还还调用了父类的getPrice()方法。虽然当前的结果复用的getPrice()方法没有什么问题,但是当当Ticket类上getPrice()的内部逻辑变化时会影响到VIPTicket子类。
- VIPTicket提供了hasExtensionalActivities()方法,但是父类并没有该方法。
- Ticket提供了refund()退款功能,而VIPTicket业务中并不需要该功能,但是由于VIPTicket继承了Ticket,所以也拥有了refund()方法。这使得代码并没有按照本意来揭示业务意图。
解决方案:
1)整理继承关系,抽取基类,创建兄弟子类
创建一个父类BasicTicket,它提供了公共的属性和方法,Ticket和VIPTicket成为兄弟子类,他们提供各自需要的方法。
2)使用组合取代继承,实现松耦合
3)使用代理取代继承
将不同的变化原因委托给不同的类。委托是类之间的常规关系,使用委托接口更加清晰,耦合度更低。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)