文章目录
三.软件(面向对象)设计原则
3.1 开闭原则(OSP)
Open-Closed Principle , OCP
3.1.1 概述
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。
3.1.2 案列
如下,分析搜狗输入法皮肤设计:
分析:搜狗输入法的皮肤是输入法背景图片、窗口颜色和声音等元素的组合。用户可以根绝自己的喜爱更换不同的输入法皮肤,也可以从网上下载新的皮肤。这些皮肤有共同的特点,可以将这些共同的的特点抽取到一个抽象类(AbstractSkin)中,而每个具体的皮肤(DefaultSkin、MySkin)是其子类。用户可以根据需要选择或者增加新的主题,而不需要修改原代码。
// 抽象类
public abstract class AbstractSkin {
public abstract void displaySkin();
}
=========================================================
//实现类
public class DefaultSkin extends AbstractSkin{
@Override
public void displaySkin() {
System.out.println("这是默认皮肤...");
}
}
=========================================================
//实现类
public class MySkin extends AbstractSkin{
@Override
public void displaySkin() {
System.out.println("这是自己的皮肤...");
}
}
=========================================================
/**
* 实现聚合的类
*/
public class SouGouInput {
//成员变量是AbstractSkin类型,(实现聚合)
private AbstractSkin skin;
public void setSkin(AbstractSkin skin) {
this.skin = skin;
}
//普通方法
public void display(){
//根据设置(setXxx)的成员变量的不同,来调用不同成员变量的皮肤
//这里的成员变量类型是抽象类型,
//所以是根据传递不同实现类的对象而调用不同的实现类里重写后的方法 来显示不同的皮肤
skin.displaySkin();
}
}
=========================================================
//测试类
public class ClinentTest {
public static void main(String[] args) {
//1. 创建搜狗输入法对象,将各种皮肤聚合到一起
SouGouInput sgi = new SouGouInput();
// 2.创建皮肤对象
//2.1 常规
// DefaultSkin ds = new DefaultSkin();
// 2.2多态
AbstractSkin ds = new DefaultSkin();
MySkin ms = new MySkin();
// 3.将皮肤设置到输入法中
sgi.setSkin(ds);
// sgi.setSkin(ms);
// 4.显示皮肤
sgi.display();//这是默认皮肤...
//当有新的输入法皮肤时候,只需重新创建一个皮肤类去继承抽象类,然后重写里面的抽象方法,再在测试类中添加即可。而不用修改之前的代码
}
}
3.2 里氏代换原则(LSP)
Liskov Substitution Principle , 简称:LSP
3.2.1 概述
里氏代换原则是面向对象设计的基本原则之一。
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,写起来虽然简单,但整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
3.2.2 案例
下面看一个里氏替换原则中经典的一个反例:
【例】正方形不是长方形。
在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形。
代码如下:
//父类 长方形
public class Rectangle {
private double length;
private double width;
public double getLength() {
return length; }
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;}
public void setWidth(double width) {
this.width = width;
}
}
======================================================
//子类(正方形) 继承父类(长方形)
//由于正方形的长和宽相同,所以在方法setLength和setWidth中,对长度和宽度都需要赋相同值。
public class Square extends Rectangle{
// 重写父类中的方法
@Override
public void setLength(double length) {
super.setLength(length);
super.setWidth(length);
}
// 重写父类中的方法
@Override
public void setWidth(double width) {
super.setWidth(width);
super.setLength(width);
}
}
======================================================
//测试类
public class Test01 {
public static void main(String[] args) {
// 创建长方形对象
Rectangle r = new Rectangle();
// 设置长宽
r.setWidth(6);
r.setLength(8);
// 扩宽方法
resize(r);
// 打印扩宽后的长和宽
printLengthWidth(r);//8.0 , 9.0
//====以下演示 违背里氏代换原则的效果====
// 创建正方形对象
Square s = new Square();
// 设置正方形的长或者宽
s.setLength(8);
//resize()方法中的形参是父类类型,所以可以传递子类的类型
//是多态形式
resize(s);
printLengthWidth(s);//执行到这里会死循环,知道内存溢出才停止
//所以根据里氏代换原则:任何基类可以出现的地方,子类一定可以出现
//但尽量不要重写父类的方法,如果重写会程序会出问题,比如此处的死循环
}
//扩宽方法
public static void resize(Rectangle r){
//判断宽如果比长小,进行扩宽的操作
while (r.getWidth() <= r.getLength()){
r.setWidth(r.getWidth() + 1);
}
}
//打印长和宽
public static void printLengthWidth(Rectangle r){
System.out.println(r.