Java设计模式-内功修炼-面向对象设计原则

面向对象设计原则

知识补充

  • 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):尽量使用对象组合,而不是继承来达到复用的目的。

理解

为什么优先考虑组合、聚合而不是继承呢?

  1. 因为继承反而会增加系统构建和维护的难度以及系统的复杂性,使用继承会增加一层抽象,增加一层抽象意味着理解成本会增加,所以不可滥用继承,最好做到恰到好处。
  2. 继承会将基类的实现细节暴露给子类。
  3. 而使用组合聚合则不会有上述问题,因为使用组合聚合可以自由选择调用某个方法,不会增加系统复杂度,从而有很大的灵活性。

迪米特法则

定义

迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

理解

  • 是降低系统耦合度的一个法则,讲究不要和陌生人说话,只和自己的直接朋友说话。所谓直接朋友有5种:

    • 当前对象本身(this);
    • 以参数形式传入到当前对象方法中的对象;
    • 当前对象的成员对象;
    • 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
    • 当前对象所创建的对象;
  • 使用该原则能够降低耦合度,从而更容易做到松耦合。

  • 为什么需要松耦合呢?因为松耦合能够增加系统的可复用性。

  • 在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

  • 举一个挺有代表意义的例子,UI编程中按钮触发的事件:
    在这里插入图片描述
    讲解:

    • 上图中按钮和多个对象发生了交互,这种情况下,试想如果我们要将按钮更换成其他控件,那么涉及到按钮的代码都需要修改,所以说这种方案的扩展性很差。
    • 为了增加可扩展性,我们考虑增加一个中间类,用来管理控件之间的交互。
      在这里插入图片描述
  • 这样我们将控件之间错综复杂的交互关系交给了中间类去处理,界面控件之间不再发生直接引用,而是将请求转发给中间类,再由中间类完成对其他控件的调用。

下一节传送门
创建型设计模式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值