面向对象设计原则
知识补充
- DAO:Data Access Object,数据访问对象,一般是数据库接口。
- 请读者自行了解java反射机制。
单一职责原则
定义
单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
理解
- 本原则讲究一个类不能太大,因为一个类承担的职责越多,其可复用性就越小。
- 个人体会:很多时候在无形中使用了该模式。比如Android开发中,各种Util就是一个很好的例子,如官方的TextUtils,当然也可以自己写各种Util。
- 个人应用:笔者目前在开发一个Android项目,可以将Utils包里的东西给大家看看,Utils包里的类个方法大多都满足单一职责原则:
- 然后再看看DensityUtils类的内容:
import android.content.Context; import android.util.TypedValue; public class DensityUtils { public static int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); } public static int sp2px(Context context, float spVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, context.getResources().getDisplayMetrics()); } public static float px2dp(Context context, float pxVal) { final float scale = context.getResources().getDisplayMetrics().density; return (pxVal / scale); } public static float px2sp(Context context, float pxVal) { return (pxVal / context.getResources().getDisplayMetrics().scaledDensity); } }
- 该类的功能就是做单位转换,Android中有很多单位,有时需要进行统一,所以就需要写一些转换函数。开发过程中发现很多地方都会使用这样的方法,所以将他们提取出来,做成一个工具类。
- 该类的职责是单一的:单位转换。
开闭原则
定义
开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
理解
-
个人体会及应用:这个原则贯穿了下面的所有原则,可以说很多模式都是为了满足开闭原则而生。其基本含义是:扩充新功能不可修改原有代码,但可以增加代码。
来看一个例子:package principle.open_closed.version1; class Chart { public class PieChart { PieChart() { } void display() { System.out.println("This is pieChart"); } } public class BarChart { BarChart() { } void display() { System.out.println("This is BarChart"); } } void ChartDisplay(String type) { if (type.equals("pie")) { PieChart pieChart = new PieChart(); pieChart.display(); } else if (type.equals("bar")) { BarChart barChart = new BarChart(); barChart.display(); } } } =======================分割线======================== package principle.open_closed.version1; import org.junit.Test; public class ChartTest { @Test public void testChart() { Chart chart=new Chart(); chart.ChartDisplay("pie"); } }
如上所示:如果我现在要增加一种新的图表,那么毫无疑问我要修改if else条件语句,很显然是违背了开闭原则,这里给出的优化方案是新建一个图表抽象类,以后需要新增图表只需要继承抽象类,然后客户端修改下new语句即可。
package principle.open_closed.version2; public class NewChart extends Chart { @Override public void display() { System.out.println("This is a newChart"); } } =========================分割线========================= package principle.open_closed.version2; import org.junit.Test; public class testChart { @Test public void testChart2() { //用户需要NewChart Chart chart = new NewChart(); chart.display(); } }
其实能看到,上面的方案还存在问题,即我要换图表依旧要修改客户端代码,此方案虽然有改进,但并没有完全能够遵循开闭原则。
我们依靠java的反射机制来设计最佳的解决方案:
package util; import jdk.jfr.events.ExceptionThrownEvent; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; public class XMLUtil { //读取配置文件,并返回类型名 public static String getClassType(String nodeName) { try { //创建文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File("config/config.xml")); //获取包含图表类型的文本结点 NodeList nl = doc.getElementsByTagName(nodeName); Node classNode = nl.item(0).getFirstChild(); String name = classNode.getNodeValue(); // System.out.println("getClassType name is:" + name); return name; } catch (Exception e) { e.printStackTrace(); return null; } } public static Object getBean(String nodeName) { try { Class c = Class.forName("principle.open_closed.version2." + getClassType(nodeName)); return c.newInstance(); } catch (Exception e) { e.printStackTrace(); return null; } } } ==============================分割线================================ package principle.open_closed.version2; import org.junit.Test; import util.XMLUtil; public class testChart { @Test public void testChart2() { //用户需要NewChart Chart chart = (Chart) XMLUtil.getBean("chartType"); chart.display(); } }
当客户要换图表的时候,只需要修改配置文件即可,这样就能够完全遵循开闭原则了。
<?xml version="1.0"?> <config> <chartType>NewChart</chartType> </config>
-
个人领会:从今以后认真遵循开闭原则,好好利用配置文件。用上配置文件后,代码不用改动,这意味着只编译一次就OK,变化的需求体现在配置文件中。
里氏替换原则
定义
里氏代换原则:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。这个定义比较拗口且难以理解,因此我们一般使用它的另一个通俗版定义: 里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
理解
-
个人体会:这个原则也很牛,无形中影响了我们的日常编程,举个例子:
@Test public void testList() { List list; list = new ArrayList(); }
大家都写过上面的代码,我们看下ArrayList的源码:
很明显可以看出ArrayList实现了List接口,我们可以大致的理解成List是父类,ArrayList是子类,上面代码中,我们声明一个List类型,而new一个ArrayList类型,也就是说List类型可以替换成ArrayList类型,此即里氏替换原则,再去上面看看定义就理解了。 -
个人应用
List的例子大家都很熟悉,在无形中应用了里氏替换原则。在写安卓的时候这种例子很常见,比如设置各种事件监听的代码,下面举个例子。首先声明一个接口类型:
public interface ModifyListener { void modify(); }
然后在某一个类中定义一个监听
private ModifyListener listenerSendCardFrag;
当需要设定该监听的时候:
MyApplication.businessCard.setListenerSendCardFrag(new BusinessCard.ModifyListener() { @Override public void modify() { updateView(); } });
- 还是比较容易理解的,最后面一个代码块new了一个匿名类,该匿名类可以理解成是接口的子类,即我们可以用子类来替换父类,此即里氏里氏替换原则的使用。
- 其实上面开闭原则中的代码(Char类型替换成NewChart类型)也体现了这个原则。
依赖倒转原则
定义
依赖倒转原则:针对接口编程,而不是针对实现编程(多数配合java反射机制使用)。
理解
其实这些原则大多都是体现一个抽象的含义,下图中的config.xml再次使用java反射机制:
- 个人体会:为什么说要针对接口编程呢?因为接口作为父类是可以被其子类替换的,也就是说我们把参数类型设定成接口类型,在后面编程时实现了具体的子类,可以无错误的将子类作为参数传进去。
- 如上图所示,DataConvertor就是接口类(类似于抽象类)即父类,TextDataConvertor以及ExcelDataConvertor为子类。
- 当我们需要在TextDataConvertor与ExcelDataConvertor两者之间频繁更换的时候,将参数类型声明为接口类型即可,我们不需要修改参数类型,将需要的类型写在配置文件中,从而实现不修改代码就能修改需求。
接口隔离原则—笔者更愿意称其为接口单一职责原则
定义
接口隔离原则:接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
理解
- 很好理解,可以将其理解成接口的单一职责原则,即每个接口不能太累,职责要单一化。
- 可以思考一番:如果一个接口十分庞杂,那么实现该接口的类都需要实现一堆方法,如果这些方法中有些是累赘的、无用的,那么就说明目前的接口太累了,需要简化其工作。
合成复用原则
定义
合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的。
理解
为什么优先考虑组合、聚合而不是继承呢?
- 因为继承反而会增加系统构建和维护的难度以及系统的复杂性,使用继承会增加一层抽象,增加一层抽象意味着理解成本会增加,所以不可滥用继承,最好做到恰到好处。
- 继承会将基类的实现细节暴露给子类。
- 而使用组合聚合则不会有上述问题,因为使用组合聚合可以自由选择调用某个方法,不会增加系统复杂度,从而有很大的灵活性。
迪米特法则
定义
迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。
理解
-
是降低系统耦合度的一个法则,讲究不要和陌生人说话,只和自己的直接朋友说话。所谓直接朋友有5种:
- 当前对象本身(this);
- 以参数形式传入到当前对象方法中的对象;
- 当前对象的成员对象;
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
- 当前对象所创建的对象;
-
使用该原则能够降低耦合度,从而更容易做到松耦合。
-
为什么需要松耦合呢?因为松耦合能够增加系统的可复用性。
-
在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
-
举一个挺有代表意义的例子,UI编程中按钮触发的事件:
讲解:- 上图中按钮和多个对象发生了交互,这种情况下,试想如果我们要将按钮更换成其他控件,那么涉及到按钮的代码都需要修改,所以说这种方案的扩展性很差。
- 为了增加可扩展性,我们考虑增加一个中间类,用来管理控件之间的交互。
-
这样我们将控件之间错综复杂的交互关系交给了中间类去处理,界面控件之间不再发生直接引用,而是将请求转发给中间类,再由中间类完成对其他控件的调用。
下一节传送门
创建型设计模式