论开闭原则在JAVA程序设计中的应用
2017-03-06郑朝霞
郑朝霞
摘要:在使用Java语言开发软件过程中,好的软件产品必须具有可扩展性好、可维护性及可复用性,也就是软件系统要符合开闭原则的。该文讨论了在软件开发中如何引入接口来实现多态,从而使软件系统满足开闭原则。
关键词:开闭原则;Java语言;多态性
中图分类号:TP311 文献标識码:A 文章编号:1009-3044(2016)30-0262-03
1 背景
在学习JAVA程序设计的过程中,经常会看到这样的话语:正是由于接口和抽象类的存在才赋予java强大的面向对象的能力。但对于初学者来说,要理解抽象类和接口的语法很容易,要理解抽象类和接口的本质作用却比较难。如果我们结合面向对象的设计模式中的开闭原则来理解接口和抽象类的作用,则会比较透彻、容易理解。1988年,Bertrand Meyer在他的著作《Object Oriented Software Construction》
中提出了开闭原则(OCP,Open-Closed Principle),开闭原则的描述比较简单,具体如下:Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.软件实体(类,模块,方法等等)应当对扩展开放,对修改关闭。其中open for extension(对扩展开放)指的是当出现新需求的时候,可以通过扩展现有软件实体达到目的。开闭原则中“open”,是指软件组件功能的扩展是开放的,允许对其进行功能扩展。”Close for modification”(对修改关闭)指的是不允许对该实体做任何修改,“close”是指对于原有代码的修改是封闭的,即不应该修改原有的代码。也就是说,需要执行多样行为的软件实体应该设计成不需要修改就可以实现各种需求的变化,坚持开闭原则可以大大减轻软件项目维护的工作量。
开闭原则的核心就是:软件实体应当对扩展开放,对修改关闭。
实现开闭原则的关键就在于“抽象”,而不是基于“实现”来编程。下面我们来看看一个基于“实现”编程的例子。
假如我们要实现这样一个需求:爸爸喝绿茶。通过这个需求,我们分析其中包含两个类:爸爸类和绿茶类。两个类之间的关系如下:
用JAVA语言实现的代码如下:
public class GreenTea {
private String color;
private String teaName;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getTeaName() {
return teaName;
}
public void setTeaName(String teaName) {
this.teaName = teaName;
}
public String use() {
return "绿茶是未经发酵制成的茶,因此较多地保留了鲜叶的天然物质.冲泡绿茶时,水温控制在80℃~90℃左右...";
}
}
class Daddy {
public void drink(GreenTea gTea) {
System.out.println("爸爸喜欢喝" +gTea.getName());
System.out.println(gTea.use());
}
}
public class DrinkTest {
public static void main(String[] args) {
Daddy dad = new Daddy();
GreenTea gt = new GreenTea();
gt.setName(“绿茶”);
gt.setColor(“红茶”);
System.out.println(“我是”+gt.getName()+”,颜色为:”+gt.getColor);
dad.drink(gt);
}
}
在上述代码中,我们可以看到,这是基于实现来进行的编程,Daddy类是进行业务逻辑处理的,属于高层模块,GreenTea类是低层模块,负责基本的原子操作。在这段代码中,Daddy类依赖GreenTea类,也就是高层模块依赖低层模块,我们看看会带来什么样的问题。
在实际的软件开发中,需求总是在发生变化,假如需求变成这样:爸爸因为胃不好,改喝红茶了。红茶的代码如下:
class BlackTea {
public String use() {
return "红茶是一种发酵而成的茶,其冲泡方法更是丰富多彩,玻璃杯冲泡法、瓷杯冲泡法......";
}
}
现在我们就会发现,必须要修改负责业务逻辑处理的Daddy类,爸爸才能喝红茶,如果爸爸的需求不断发生变化,我们就得不断的修改Daddy类,很显然上述代码不符合开闭原则。
而在实际的软件开发中,修改业务逻辑处理的高层模块将会带来风险,引入错误,对于后期的软件开发和维护带来很多的问题。
我们必须修改设计,让它符合开闭原则。
2 开闭原则的应用
上述代码显然不符合开闭原则,因为进行业务逻辑处理的Daddy类和GreenTea类的耦合度太高,需要降低它們之间的耦合度!那么如何才能降低耦合度。
解决该问题的最关键因素在于抽象化,在编辑语言当中,系统可以将抽象设计定义为较为固定的设计方式,利用Java语言,可以定义多个接口。可以扩展实现类,而抽象层都不会改变,这就进一步使得开闭原则的第二条得到满足,对修改进行关闭。此外,系统的行为可以通过从抽象层导出多个新的实现类而改变。由于系统设计是开放的,这与开闭原则的第一条存在着高度的一致性。
在这里我们设计了一个三层的结构,通过引入接口和抽象类来实现开闭原则。第一层是接口ITea,定义了一个use()方法;第二层是抽象类Tea,实现了ITea接口;第三层是具体的实现类,可以扩展很多个。
接口ITea只定义子类应该实现的方法use(),而没有实现公有的功能。在ITea接口中只有一个方法use(),专门进行茶的处理, 由所有的实现类实现该方法。同时 ITea作为接口应该是稳定且可靠的,不应该经常发生变化,否则接口做为契约的作用就失去了效能。
引入了抽象类Abstrac class Tea,实现了公有的功能setColor()、getColor()、setName()、getName,但并没有实现use()方法,use()针对抽象茶类进行编程,由具体的实现类分别去实现各自的use()方法。并通过构造方法由客户端来设置实例化的具体茶对象。如果需要增加一种新的茶,如咖啡Coffee,只需要将Coffee也作为Tea的子类,无须修改现有类库的源代码。
现阶段在大部分的软件在开发的过程当中,都定义了实现类的接口,通过这种接口的定义,只需要改变一个实现类就可以实现所有实体的转变。
通过引入接口和抽象类,就可以让负责业务逻辑处理的Daddy类依赖抽象类Tea,而不是依赖实现类GreenTea类、BlackTea类。也就是高层模块应该依赖抽象,而不是具体的实现。如图所示,各子类由接口类定义了接口方法,只需要在不同的子类中编写不同的实现即可,当然也可以扩展自有的方法。
修改后的设计如下:
修改设计后的实现代码如下:
这个是接口ITea,主要定义方法use()
public interface ITea {
public String use();
}
这个是抽象类Tea,抽象类Tea实现了接口ITea
public abstract class Tea implements ITea{
private String color;
private String teaName;
public ITea(String teaName, String color) {
this.teaName = teaName;
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getTeaName() {
return teaName;
}
public void setTeaName(String teaName) {
this.teaName = teaName;
}
public abstract String use();
}
这个是实现类GreenTea,继承了抽象类Tea, 实现了use()方法。我们可以扩展很多类似的实现类。
public class GreenTea extends Tea {
public String use() {
return "绿茶是未经发酵制成的茶,因此较多地保留了鲜叶的天然物质.冲泡绿茶时,水温控制在80℃~90℃左右...";
}
}
扩展的实现类红茶类BlackTea,同样也是继承了抽象类Tea,实现了use()方法。我们不需要修改其他的设计,只需要扩展实现类就可以了。
class BlackTea extends Tea {
public String use() {
return "红茶是一种发酵而成的茶,其冲泡方法更是丰富多彩,玻璃杯冲泡法、瓷杯冲泡法......";
}
}
这个是业务逻辑处理类Daddy,依赖于抽象类Tea.
class Daddy {
public void drink(Tea tea) {
System.out.println("爸爸喜欢喝"+tea.getTeaName()+"!");
System.out.println("颜色为:"+tea.getColor()+"!");
System.out.println(tea.use());
}
}
下面的代码主要为了测试软件的功能,是不是实现了开闭原则。
public class DrinkTest {
public static void main(String[] args) {
Daddy dad = new Daddy();
GreenTea gt = new GreenTea(“绿茶”,”绿色”);
BlackTea bt = new BlackTea(“红茶”,”褐色”);
dad.drink(gt);
dad.drink(bt);
}
}
上面的程序是利用Java语言的多态性来实现的软件开闭原则。多态性是指程序中定义的父类引用变量所指向的对象和该引用变量所进行的方法调用在代码编写时并不确定,该引用变量所指向的对象往往都是在程序运行的过程当中才决定的,以及该变量的调用究竟是在哪个子类当中所实现的,同时这也必须是要在程序运行的过程当中才可以实现的。这是由于具体的子类是在程序运行的过程当中才被决定下来的,通过这种方式,源代码并不需要 经过修改就可以被直接实现,在这个过程当中调用的具体方法也会随着被改变,同时程序在运行过程当中所绑定的代码也会被随着改变,同时该程序也可以以多个状态运行,这就是所谓的多态性,正是因为具有多态性才导致软件具有较强的扩展性与灵活性。
在Daddy类中,drink(Tea tea)中父类类型(Tea)的引用将指向子类的对象(GreenTea、BlackTea等等),在程序运行的过程中,根据指向的不同实现类,将调用不同的子类对象的use()方法。GreenTea、BlackTea等实现类继承抽象类Tea,并实现了Tea的use()方法。由于GreenTea、BlackTea实现了use()方法,那么父类类型的引用gt在调用该方法时将会调用子类中重写的use()方法。
也就是说,如果爸爸的需求不断发生变化,我们不需要修改进行业务逻辑处理的Daddy类,只需要扩展子类。如果爸想喝茉莉花茶,我们就扩展一个JasmineTea类,增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。
很显然上述代码体现了JAVA语言的多态性,软件设计符合开闭原则。
3 结束语
大多数的程序设计都需要与闭合原则存在一定的一致性,开闭原则也是对不同模块进行评价的基本依据,我们可以利用开闭原则来判断系统的扩展性与灵活性。
绝大部分的设计模式都必须符合开闭原則,在对每一个模式进行优缺点评价时都会以开闭原则作为一个重要的评价依据,以判断基于该模式设计的系统是否具备良好的灵活性和可扩展性。
所以,我们在实际的软件开发中,需要通过扩展来实现业务逻辑的变化,而不是修改。如果一个软件系统符合开闭原则的,那么从软件工程的角度来看,它至少具有这样的好处:可扩展性好、可维护性好。
参考文献;
[1] 萨默维尔.软件工程[M]. 程成, 译. 9版.北京: 机械工业出版社, 2011.
[2] Metsker S J.Java设计模式[M]. 2版.北京: 电子工业出版社, 2012.
[3] 赵亚娟. 计算机软件JAVA编程特点及其技术研究[J]. 数字技术与应用, 2016(1): 113.
[4] 葛萌, 张琳娜, 陈伟. Java多态性机制应用研究[J]. 攀枝花学院学报, 2016(2): 25-28.
[5] 罗诗敏. 基于MVC的学生信息管理系统的分析与设计[D]. 广州: 华南理工大学, 2014