您的位置:首页 > 其它

[知识池]彻底了解设计模式(系列二)

2017-08-22 10:20 148 查看
这个系列来讲讲面向对象设计的几个原则一、单一职责原则单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小。单一职责原则定义如下: 单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验【难点:人们如何来将不同的类进行职责分离呢?】举个实际的案例Sunny软件公司开发人员针对某CRM(Customer Relationship Management,客户关系管理)系统中客户信息图形统计模块提出了如图1所示初始设计方案:图1 初始设计方案结构图在图1中,CustomerDataChart类中的方法说明如下:getConnection()方法用于连接数据库,findCustomers()用于查询所有的客户信息,createChart()用于创建图表,displayChart()用于显示图表。按自己的理解:这个类实现的功能有点多,包括了数据库连接,查询数据,展示数据。如果要在其他的类里面也要实现数据库查询那就没有办法实现数据库查询的复用,当我们要修改数据库连接或者说修改图表展示那都需要修改这个类,它不止一个引起它变化的原因,这个就违背了单一职责原则。那就需要我们进行类进行拆分。怎么拆呢?1)DBUtil:负责连接数据库,包含数据库连接方法getConnection();(2) CustomerDAO:负责操作数据库中的Customer表,包含对Customer表的增删改查等方法,如findCustomers();+(3) CustomerDataChart:负责图表的生成和显示,包含方法createChart()和displayChart()。这样每个类就实现自己那块的功能就行了。这个在我们做业务开发过程中也是经常会用到,哪些功能需要拆成单独的一个bizService二、开闭原则定义:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。(可以编写可扩展的软件)。在开闭原则的定义中,软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。旁白:只需要我们定义好抽象的父类或者说是抽象的接口,然后后面的开发就是不断去写它的实现类就行。+Sunny软件公司开发的CRM系统可以显示各种类型的图表,如饼状图和柱状图等,为了支持多种图表显示方式,原始设计方案如图1所示:+在ChartDisplay类里面的display()方法里面可能会写这样的代码:if(type.equals("pie")) {   PieChart chart = new PieChart();   chart.display();}else if(type.equals("bar")) {   BarChart chart = new BarChart();   chart.display();}将来要是扩展一个新的图表类那就得修改这个里面的代码,这就违反了开闭原则。现在重构一下:1)增加一个抽象的图表类AbstractChart,将各种具体的图表类作为子类;2)ChartDisplay类针对抽象图表类进行编程,由客户端来决定使用哪种具体图表。示例代码:
public abstract class AbstractChart {
public void display() {}
}
public class PieChart extends AbstractChart {
@Override
public void display(){
System.out.println("this is pie chart");
}
}
public class BarChart extends AbstractChart {
@Override
public void display() {
System.out.println("this is bar chart");
}
}
public class CharDisplay {

private AbstractChart chart;

public void setChart(AbstractChart chart){
this.chart = chart;
}

public void display(){
chart.display();
}

public static void main(String[] args) {
CharDisplay charDisplay = new CharDisplay();
charDisplay.setChart(new BarChart());// 设置这个具体的实现类。在调用的时候自己指定用哪个实现类
charDisplay.display();
}
}
三、里氏代换原则
里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。里氏代换原则
告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件
实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,
因为我并不喜欢老鼠,虽然它也是动物
有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受
一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,
method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:
method2(sub),那么一般而言不可以有method2(base),除非是重载方法。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。在使用里氏代换原则时需要注意如下几个问题:(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的在Sunny软件公司开发的CRM系统中,客户(Customer)可以分为VIP客户(VIPCustomer)和普通客户(CommonCustomer)两类,系统需要提供一个发送Email的功能,原始设计方案如图1所示:在对系统进行进一步分析后发现,无论是普通客户还是VIP客户,发送邮件的过程都是相同的,也就是说两个send()方法中的代码重复,而且在本系统中还将增加新类型的客户。为了让系统具有更好的扩展性,同时减少代码重复,使用里氏代换原则对其进行重构。在本实例中,可以考虑增加一个新的抽象客户类Customer,而将CommonCustomer和VIPCustomer类作为其子类,邮件发送类EmailSender类针对抽象客户类Customer编程,根据里氏代换原则,能够接受基类对象的地方必然能够接受子类对象,因此将EmailSender中的send()方法的参数类型改为Customer里氏代换原则是实现开闭原则的重要方式之一。在本实例中,在传递参数时使用基类对象,除此以外,在定义成员变量、定义局部变量、确定方法返回类型时都可使用里氏代换原则。针对基类编程,在程序运行时再确定具体子类。+
参考链接:https://gof.quanke.name
                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: