精华内容
下载资源
问答
  • 使用组合复用原则的解法; 代码实现: //采用继承实现 class Person{ } class Teachers extends Person{} class Students extends Person{} //小学教师 class PrimmarySchoolTeacher extends Teachers{} //初中...

    场景

    例题:教育局的学校管理中包括小学、初中和高中等三个级别老师与学生

    不使用组合复用原则的解法;
    在这里插入图片描述
    代码实现:

    //采用继承实现
    class Person{	}
    class Teachers extends Person{}
    class Students extends Person{}
    //小学教师
    class PrimmarySchoolTeacher extends Teachers{}
    //初中教师
    class JuniorSchoolTeacher extends Teachers{}
    //高中教师
    class HighSchoolTeacher extends Teachers{}
    //小学生
    class PrimmaryStudent extends Students{
    	public void say() {
    		// TODO Auto-generated method stub
    		System.out.println("我是小学生");
    	}
    }
    //初中生
    class JuniorStudent extends Students{
    	public void say() {
    		// TODO Auto-generated method stub
    		System.out.println("我是初中生");
    	}
    }
    //高中生
    class HighStudent extends Students{
    	public void say() {
    		// TODO Auto-generated method stub
    		System.out.println("我是高中生");
    	}
    }
    public class Demo1 {
    	public static void main(String[] args) {
    		//输出测试代码
    	}
    }
    
    

    在这里插入图片描述
    组合复用原则的概念
    优先使用对象组合(聚合),而不是继承来达到复用的目的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    使用组合复用原则的解法:
    在这里插入图片描述
    代码实现:

    //采用组合复用原则实现
    abstract class Person{
            private String name;
            private SchoolLevel level;//学校层次与人事关系(组合)
            public Person(String name,SchoolLevel level){
                   super();
                    this.name = name;
                    this.level = level;
            }
            public SchoolLevel getLevel(){
                    return level;
            }
            public void setLevel(SchoolLevel level){
                    this.level = level;
            }
    }
    //教师类
    class Teacher extends Person{
            public Teacher(String name,SchoolLevel level){
                    super(name,level);
            }
            public void say(){
               System.out.println(super.getName()+"是"+super.getLevel()+"老师");
            }
    }
    //学生类
    class Student extends Person{
            public Student(String name,SchoolLevel level){
                    super(name,level);
            }
            public void say(){             													     System.out.println(super.getName()+"是"+super.getLevel()+"学生");
            }
    }
    //学校层次
    interface SchoolLevel{
            String getLevel();
    }
    //小学
    class PrimaryLevel implements SchoolLevel{
            @Override
            public String getLevel(){
                return "小学";
            }
    }
    //初中
    class JuniorLevel implements SchoolLevel{
            @Override
            public String getLevel(){
                return "初中";
            }
    }
    //高中
    class SenioLevel implements SchoolLevel{
            @Override
            public String getLevel(){
                return "高中";
            }
    }
    //后勤人员
    class Logistics extends Person{
            public Logistics (String name,SchoolLevel level){
                    super(name,level);
            }
    }
    //测试
    public class Demo{
            public static void main(String[] args){
                  SchoolLevel Level =new SenioLevel();//高中
                  Student swk =  new Student("孙悟空",level);
                  swk.say();
            } 
    }
    

    运行截图:
    在这里插入图片描述

    展开全文
  • 场景 例题:学生管理类StudentManager包含...使用单一职责原则的解法: public class Demo { public void getConnection(String uri) { System.out.println("与"+uri+"建立联系"); } public void searchStudent(S

    场景

    例题:学生管理类StudentManager包含如下功能,包括连接数据库方法getConnection()、操作数据库方法入search和delete、显示学生信息的方法display。

    不使用单一职责原则的解法:

    public class Demo {
        public void getConnection(String uri) {
            System.out.println("与"+uri+"建立联系");
        }
        public void searchStudent(String condition) {
            System.out.println("根据"+condition+"查询学生");
        }
        public void deleteStudent(String condition) {
            System.out.println("根据"+condition+"删除学生");
        }
        public void display() {
            System.out.println("显示学生信息!");
        }
    //学生类有三个职责,连接数据库,查询、删除数据,显示查询结构
    //存在问题
    //1.如果需要修改连接的数据库服务器,需要修改这个类
    //2.如果需要修改查询方式,或者增加插入学生数据,需要修改这个类。
    //有两个引起变化的原因,职责不单一。
    

    单一职责原则概念:
    在这里插入图片描述
    在这里插入图片描述
    使用单一职责原则的解法:

    类图:
    在这里插入图片描述
    代码实现:

    //优点
    //1.DBUtil封装了数据库连接操作,更改数据库服务器仅仅修改这个类或者配置文件
    //2.studentDAO封装了操作数据库的方法,增减插入、更改查询方式等方法比较容易
    //学生管理类,仅仅显示学生信息,职责单一
    public class studentManager {
        private studentDAO dao;
        public studentManager(studentDAO dao) {
            super();
            this.dao = dao;
        }
        public void display() {
            System.out.println("显示学生信息!");
        }
    }
    //数据库操作类,职责单一
    public class studentDAO {
        private DBUtil db;
        public studentDAO(DBUtil db) {
            super();
            this.db = db;
        }
        public void searchStudent(String condition) {
            System.out.println("根据"+condition+"查询学生");
        }
        public void deleteStudent(String condition) {
            System.out.println("根据"+condition+"删除学生");
        }
    }
    //数据库工具类,职责单一
    public class DBUtil {
        public void getConnection(String uri) {
            System.out.println("与"+uri+"建立联系");
        }
    }
    //实例测试类
    public class demo {
        public static void main(String[] args) {
            DBUtil db=new DBUtil();
            studentDAO dao=new studentDAO(db);
            studentManager sm=new studentManager(dao);
            db.getConnection("MYSQL");
            dao.searchStudent("孙悟空");
            sm.display();
        }
    }
    

    运行截图:
    在这里插入图片描述

    展开全文
  • 软件设计的原则

    2020-08-04 21:17:23
    计算5+6和,就是这个小小话题,我们变化了7次,包括代码的改变和新需求一次次出现,一步步变化,一步步思考,刷新了我们认识,也让我知道了代码这个神奇东西,还有条条大路通罗马道理,但是不是每...

    最近两天,开始学习代码优化,架构之路的探索。不得不说,收获了很多。
    在这里插入图片描述

    在这里插入图片描述

    每次变化一点点
    计算5+6的和,就是这个小小的话题,我们变化了7次,包括代码的改变和新的需求一次次的出现,一步步的变化,一步步的思考,刷新了我们的认识,也让我知道了代码这个神奇的东西,还有条条大路通罗马的道理,但是不是每条路都是最快的,也不是每条路都是最顺畅的,我们敲代码就要走那条最快最顺畅的。

    魔法值到隔离的距离有多远
    计算5+6的和,从最初的输出两个魔法值的和,到后边前后端分离计算任何数字的各种运算,从魔法值到复用,变量到封装、复用, 没有魔法值,前后端的分离,隔离分层的一步步转变,看似我们变化了七次,没有很大的变化,但是其实每一步都是一大步,都是巨大的进步,都是思想的极大变化和提升。

    软件的设计要做到怎样
    虽然计算5+6的方式有很多很多,但是我们做软件设计,是不仅仅要做到只能计算5+6,更是在这个的基础上,去更多的思考,将5.+,6都去抽象出来,把他们用我们的眼睛,用我们的大脑,隔离开来,对于数字,我们怎么获得,怎么更加灵活的去获得,去拓展,如果不是5,而是别的数字,而是别的数据类型,,对于“+”,我们抽象出来,怎么更加灵活的改变符号,这些在编程的世界,不再是一个平面上的静止的东西,而是在我们的编程维度的世界里每一个小世界的存在,他们是立体的,是抽象的。

    软件设计要尽可能的做到,灵活,分层,隔离,解耦,单一职责,开发,可复用……,需要我们经常开动我们的小脑筋,去思考怎么样让我们的代码更加的灵活。拥有不将就的习惯,是我们需要不断追求的。

    面向对象的思想,隔离分层的思想,开放封闭的思想,高内聚低耦合的思想,这些需要我们刻意练习,需要我们多多的实践。

    想象一下,当我们再次遇到计算两个数的和,这样的代码实践,我们第一步是直接去写出两个魔法值还是说仔细思考一下,考虑长远一点写一个灵活的代码可以复用的代码呢??你品,你细品,如果你做到的后边一点,那说明,你进步了。

    展开全文
  • 面向对象的设计原则

    2017-12-18 00:21:00
     面向对象的设计原则,可以说每种设计模式都是为了让代码迎合其中一个或多个原则而出现, 它们本身已经融入了设计模式之中,给面向对象编程指明了方向。适合javascript开发的设计原则包括是单一职责原则、最少...

    前面的话

      面向对象的设计原则,可以说每种设计模式都是为了让代码迎合其中一个或多个原则而出现的, 它们本身已经融入了设计模式之中,给面向对象编程指明了方向。适合javascript开发的设计原则包括是单一职责原则、最少知识原则和开放封闭原则。本文将详细介绍面向对象的设计原则

     

    单一职责原则

      就一个类而言,应该仅有一个引起它变化的原因。在javascript中,需要用到类的场景并不太多,单一职责原则更多地是被运用在对象或者方法级别上

      单一职责原则(SRP)的职责被定义为“引起变化的原因”。如果有两个动机去改写一个方法,那么这个方法就具有两个职责。每个职责都是变化的一个轴线,如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。此时,这个方法通常是一个不稳定的方法,修改代码总是一件危险的事情,特别是当两个职责耦合在一起的时候,一个职责发生变化可能会影响到其他职责的实现,造成意想不到的破坏,这种耦合性得到的是低内聚和脆弱的设计。因此,SRP原则体现为:一个对象(方法)只做一件事情

      SRP原则在很多设计模式中都有着广泛的运用,例如代理模式、迭代器模式、单例模式和装饰者模式

    【代理模式】

      通过增加虚拟代理的方式,把预加载图片的职责放到代理对象中,而本体仅仅负责往页面中添加img标签,这也是它最原始的职责

      myImage负责往页面中添加img标签:

    var myImage = (function(){
        var imgNode = document.createElement( 'img' );
        document.body.appendChild( imgNode );
        return {
            setSrc: function( src ){
    
                imgNode.src = src;
            }
        }
    })();

      proxyImage负责预加载图片,并在预加载完成之后把请求交给本体 myImage:

    var proxyImage = (function(){
        var img = new Image;
        img.onload = function(){
            myImage.setSrc( this.src );
        }
        return {
            setSrc: function( src ){
                myImage.setSrc( 'file://loading.gif' );
                img.src = src;
            }
        }
    })();
    proxyImage.setSrc( 'http://test.jpg' );

      把添加img标签的功能和预加载图片的职责分开放到两个对象中,这两个对象各自都只有一个被修改的动机。在它们各自发生改变的时候,也不会影响另外的对象

    【迭代器模式】

      有这样一段代码,先遍历一个集合,然后往页面中添加一些div,这些div的innerHTML分别对应集合里的元素:

    var appendDiv = function( data ){
      for ( var i = 0, l = data.length; i < l; i++ ){ 
        var div = document.createElement( 'div' ); 
        div.innerHTML = data[ i ]; 
        document.body.appendChild( div );
      }
    };
    appendDiv( [ 1, 2, 3, 4, 5, 6 ] );

      这其实是一段很常见的代码,经常用于ajax请求之后,在回调函数中遍历ajax请求返回的数据,然后在页面中渲染节点。appendDiv函数本来只是负责渲染数据,但是在这里它还承担了遍历聚合对象data的职责。如果有一天cgi返回的data数据格式从array变成了object,那遍历data的代码就会出现问题,必须改成for in的方式,这时候必须去修改appendDiv里的代码,否则因为遍历方式的改变,导致不能顺利往页面中添加div节点

      有必要把遍历data的职责提取出来,这正是迭代器模式的意义,迭代器模式提供了一种方法来访问聚合对象,而不用暴露这个对象的内部表示。

      当把迭代聚合对象的职责单独封装在each函数中后,即使以后还要增加新的迭代方式,只需要修改each函数即可,appendDiv函数不会受到牵连,代码如下:

    var each = function( obj, callback ) {
        var value,
        i = 0,
        length = obj.length,
        isArray = isArraylike( obj ); // isArraylike 函数未实现
        if ( isArray ) { // 迭代类数组
            for ( ; i < length; i++ ) {
                callback.call( obj[ i ], i, obj[ i ] );
            }
        } else {
            for ( i in obj ) { // 迭代object 对象
                value = callback.call( obj[ i ], i, obj[ i ] );
            }
        }
        return obj;
    };
    
    var appendDiv = function( data ){
        each( data, function( i, n ){
            var div = document.createElement( 'div' );
            div.innerHTML = n;
            document.body.appendChild( div );
        });
    };
    
    appendDiv( [ 1, 2, 3, 4, 5, 6 ] );
    appendDiv({a:1,b:2,c:3,d:4} );

    【单例模式】

      下面是一段代码

    var createLoginLayer = (function(){
        var div;
        return function(){
            if ( !div ){
                div = document.createElement( 'div' );
                div.innerHTML = '我是登录浮窗';
                div.style.display = 'none';
                document.body.appendChild( div );
            }
            return div;
        }
    })();

      现在把管理单例的职责和创建登录浮窗的职责分别封装在两个方法里,这两个方法可以独立变化而互不影响,当它们连接在一起的时候,就完成了创建唯一登录浮窗的功能,下面的代码显然是更好的做法:

    var getSingle = function( fn ){ // 获取单例
        var result;
        return function(){
            return result || ( result = fn .apply(this, arguments ) );
        }
    };
    var createLoginLayer = function(){ // 创建登录浮窗
        var div = document.createElement( 'div' );
        div.innerHTML = '我是登录浮窗';
        document.body.appendChild( div );
        return div;
    };
    
    var createSingleLoginLayer = getSingle( createLoginLayer );
    var loginLayer1 = createSingleLoginLayer();
    var loginLayer2 = createSingleLoginLayer();
    alert ( loginLayer1 === loginLayer2 ); // 输出: true

    【装饰者模式】

      使用装饰者模式时,通常让类或者对象一开始只具有一些基础的职责,更多的职责在代码运行时被动态装饰到对象上面。装饰者模式可以为对象动态增加职责,从另一个角度来看, 这也是分离职责的一种方式

      下面把数据上报的功能单独放在一个函数里,然后把这个函数动态装饰到业务函数上面:

    <button tag="login" id="button">点击打开登录浮层</button>
    <script>
        Function.prototype.after = function( afterfn ){
            var __self = this;
            return function(){
                var ret = __self.apply( this, arguments );
                afterfn.apply( this, arguments );
                return ret;
            }
        };
        var showLogin = function(){
            console.log( '打开登录浮层' );
        };
        var log = function(){
            console.log( '上报标签为: ' + this.getAttribute( 'tag' ) );
    
        };
        document.getElementById( 'button' ).onclick = showLogin.after( log );
    // 打开登录浮层之后上报数据

      SRP原则是所有原则中最简单也是最难正确运用的原则之一。要明确的是,并不是所有的职责都应该一一分离。一方面,如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们。比如在ajax请求的时候,创建xhr对象和发送xhr请求几乎总是在一起的,那么创建xhr对象的职责和发送xhr请求的职责就没有必要分开。另一方面,职责的变化轴线仅当它们确定会发生变化时才具有意义,即使两个职责已经被耦合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们,在代码需要重构的时候再进行分离也不迟

      在人的常规思维中,总是习惯性地把一组相关的行为放到一起,如何正确地分离职责不是一件容易的事情。在实际开发中,因为种种原因违反SRP的情况并不少见。比如jQuery的attr等方法,就是明显违反SRP原则的做法。jQuery的attr是个非常庞大的方法,既负责赋值,又负责取值,这对于jQuery的维护者来说,会带来一些困难,但对于jQuery的用户来说,却简化了用户的使用。在方便性与稳定性之间要有一些取舍。具体是选择方便性还是稳定性,并没有标准答案,而是要取决于具体的应用环境

      SRP原则的优点是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。但SRP原则也有一些缺点,最明显的是会增加编写代码的复杂度。当按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度

     

    最少知识原则

      最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用。这里的软件实体是一个广义的概念,不仅包括对象,还包括系统、类、模块、函数、变量等

      某军队中的将军需要挖掘一些散兵坑。下面是完成任务的一种方式:将军可以通知上校让他叫来少校,然后让少校找来上尉,并让上尉通知一个军士,最后军士唤来一个士兵,然后命令士兵挖掘一些散兵坑。这种方式十分荒谬,不是吗?不过,还是先来看一下这个过程的等价代码:

    gerneral.getColonel(c).getMajor(m).getCaptain(c).getSergeant(s).getPrivate(p).digFoxhole();

      让代码通过这么长的消息链才能完成一个任务,这就像让将军通过那么多繁琐的步骤才能命令别人挖掘散兵坑一样荒谬!而且,这条链中任何一个对象的改动都会影响整条链的结果。最有可能的是,将军自己根本就不会考虑挖散兵坑这样的细节信息。但是如果将军真的考虑了这个问题的话,他一定会通知某个军官:“我不关心这个工作如何完成,但是你得命令人去挖散兵坑。”

      单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性。但越来越多的对象之间可能会产生错综复杂的联系,如果修改了其中一个对象,很可能会影响到跟它相互引用的其他对象。对象和对象耦合在一起,有可能会降低它们的可复用性。

      最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三者对象来转发这些请求

      最少知识原则在设计模式中体现得最多的地方是中介者模式和外观模式

    【中介者模式】

      在世界杯期间购买足球彩票,如果没有博彩公司作为中介,上千万的人一起计算赔率和输赢绝对是不可能的事情。博彩公司作为中介,每个人都只和博彩公司发生关联,博彩公司会根据所有人的投注情况计算好赔率,彩民们赢了钱就从博彩公司拿,输了钱就赔给博彩公司。中介者模式很好地体现了最少知识原则。通过增加一个中介者对象,让所有的相关对象都通过中介者对象来通信,而不是互相引用。所以,当一个对象发生改变时,只需要通知中介者对象即可

    【外观模式】

      外观模式主要是为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使子系统更加容易使用

      外观模式的作用是对客户屏蔽一组子系统的复杂性。外观模式对客户提供一个简单易用的高层接口,高层接口会把客户的请求转发给子系统来完成具体的功能实现。大多数客户都可以通过请求外观接口来达到访问子系统的目的。但在一段使用了外观模式的程序中,请求外观并不是强制的。如果外观不能满足客户的个性化需求,那么客户也可以选择越过外观来直接访问子系统

      拿全自动洗衣机的一键洗衣按钮举例,这个一键洗衣按钮就是一个外观。如果是老式洗衣机,客户要手动选择浸泡、洗衣、漂洗、脱水这4个步骤。如果这种洗衣机被淘汰了,新式洗衣机的漂洗方式发生了改变,那还得学习新的漂洗方式。而全自动洗衣机的好处很明显,不管洗衣机内部如何进化,客户要操作的,始终只是一个一键洗衣的按钮。这个按钮就是为一组子系统所创建的外观。但如果一键洗衣程序设定的默认漂洗时间是20分钟,而客户希望这个漂洗时间是30分钟,那么客户自然可以选择越过一键洗衣程序,自己手动来控制这些“子系统”运转。外观模式容易跟普通的封装实现混淆。这两者都封装了一些事物,但外观模式的关键是定义一个高层接口去封装一组“子系统”。子系统在C++或者Java中指的是一组类的集合,这些类相互协作可以组成系统中一个相对独立的部分。在javascript中通常不会过多地考虑“类”,如果将外观模式映射到javascript中,这个子系统至少应该指的是一组函数的集合

      最简单的外观模式应该是类似下面的代码:

    var A = function(){
      a1();
      a2();
    }
    var B = function(){
      b1();
      b2();
    }
    var facade =function(){
      A();
      B();
    }
    facade();

      许多javascript设计模式的图书或者文章喜欢把jQuery的$.ajax函数当作外观模式的实现,这是不合适的。如果$.ajax函数属于外观模式,那几乎所有的函数都可以被称为“外观模式”。问题是根本没有办法越过$.ajax“外观”去直接使用该函数中的某一段语句

      现在再来看看外观模式和最少知识原则之间的关系。外观模式的作用主要有两点

      1、为一组子系统提供一个简单便利的访问入口

      2、隔离客户与复杂子系统之间的联系,客户不用去了解子系统的细节。从第二点来,外观模式是符合最少知识原则的

      封装在很大程度上表达的是数据的隐藏。一个模块或者对象可以将内部的数据或者实现细节隐藏起来,只暴露必要的接口API供外界访问。对象之间难免产生联系,当一个对象必须引用另外一个对象的时候,可以让对象只暴露必要的接口,让对象之间的联系限制在最小的范围之内。同时,封装也用来限制变量的作用域。在javascript中对变量作用域的规定是:

      1、变量在全局声明,或者在代码的任何位置隐式申明(不用var),则该变量在全局可见;

      2、变量在函数内显式申明(使用var),则在函数内可见。把变量的可见性限制在一个尽可能小的范围内,这个变量对其他不相关模块的影响就越小,变量被改写和发生冲突的机会也越小。这也是广义的最少知识原则的一种体现

      假设要编写一个具有缓存效果的计算乘积的函数function mult(){},需要一个对象var cache = {}来保存已经计算过的结果。cache对象显然只对mult有用,把cache对象放在mult形成的闭包中,显然比把它放在全局作用域更加合适,代码如下:

    var mult = (function(){
        var cache = {};
        return function(){
            var args = Array.prototype.join.call( arguments, ',' );
            if ( cache[ args ] ){
                return cache[ args ];
            }
            var a = 1;
            for ( var i = 0, l = arguments.length; i < l; i++ ){
    
                a = a * arguments[i];
            }
            return cache[ args ] = a;
        }
    })();
    mult( 1, 2, 3 ); // 输出: 6

      虽然遵守最小知识原则减少了对象之间的依赖,但也有可能增加一些庞大到难以维护的第三者对象。跟单一职责原则一样,在实际开发中,是否选择让代码符合最少知识原则,要根据具体的环境来定

     

    开放封闭原则

      在面向对象的程序设计中,开放——封闭原则(OCP)是最重要的一条原则。很多时候,一个程序具有良好的设计,往往说明它是符合开放——封闭原则的。开放——封闭原则的定义如下:软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改

      假设我们是一个大型Web项目的维护人员,在接手这个项目时,发现它已经拥有10万行以上的javascript代码和数百个JS文件。不久后接到了一个新的需求,即在window.onload函数中打印出页面中的所有节点数量。打开文本编辑器,搜索出window.onload函数在文件中的位置,在函数内部添加以下代码:

    window.οnlοad=function(){
      //原有代码略
      console.log(document.getElementsByTagName('*').length);
    };

      在项目需求变迁的过程中,经常会找到相关代码,然后改写它们。这似乎是理所当然的事情,不改动代码怎么满足新的需求呢?想要扩展一个模块,最常用的方式当然是修改它的源代码。如果一个模块不允许修改,那么它的行为常常是固定的。然而,改动代码是一种危险的行为,也许都遇到过bug越改越多的场景。刚刚改好了一个bug,但是又在不知不觉中引发了其他的bug

      如果目前的window.onload函数是一个拥有500行代码的巨型函数,里面密布着各种变量和交叉的业务逻辑,而需求又不仅仅是打印一个log这么简单。那么“改好一个bug,引发其他bug”这样的事情就很可能会发生。永远不知道刚刚的改动会有什么副作用,很可能会引发一系列的连锁反应

      那么,有没有办法在不修改代码的情况下,就能满足新需求呢?通过增加代码,而不是修改代码的方式,来给window.onload函数添加新的功能,代码如下:

    Function.prototype.after = function( afterfn ){
        var __self = this;
        return function(){
            var ret = __self.apply( this, arguments );
            afterfn.apply( this, arguments );
            return ret;
        }
    };
    window.onload = ( window.onload || function(){} ).after(function(){
        console.log( document.getElementsByTagName( '*' ).length );
    });

      通过动态装饰函数的方式,完全不用理会从前window.onload函数的内部实现,无论它的实现优雅或是丑陋。就算作为维护者,拿到的是一份混淆压缩过的代码也没有关系。只要它从前是个稳定运行的函数,那么以后也不会因为我们的新增需求而产生错误。新增的代码和原有的代码可以井水不犯河水

      现在引出开放——封闭原则的思想:当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码

      过多的条件分支语句是造成程序违反开放——封闭原则的一个常见原因。每当需要增加一个新的if语句时,都要被迫改动原函数。把if换成switch-case是没有用的,这是一种换汤不换药的做法。实际上,每当看到一大片的if或者swtich-case语句时,第一时间就应该考虑,能否利用对象的多态性来重构它们

      利用对象的多态性来让程序遵守开放——封闭原则,是一个常用的技巧。下面先提供一段不符合开放——封闭原则的代码。每当增加一种新的动物时,都需要改动makeSound函数的内部实现:

    var makeSound = function( animal ){
        if ( animal instanceof Duck ){
            console.log( '嘎嘎嘎' );
        }else if ( animal instanceof Chicken ){
            console.log( '咯咯咯' );
        }
    };
    
    var Duck = function(){};
    var Chicken = function(){};
    makeSound( new Duck() ); // 输出:嘎嘎嘎
    makeSound( new Chicken() ); // 输出:咯咯咯

      动物世界里增加一只狗之后,makeSound函数必须改成:

    var makeSound = function( animal ){
        if ( animal instanceof Duck ){
            console.log( '嘎嘎嘎' );
        }else if ( animal instanceof Chicken ){
            console.log( '咯咯咯' );
        }else if ( animal instanceof Dog ){ // 增加跟狗叫声相关的代码
            console.log('汪汪汪' );
        }
    };
    var Dog = function(){};
    makeSound( new Dog() ); // 增加一只狗

      利用多态的思想,把程序中不变的部分隔离出来(动物都会叫),然后把可变的部分封装起来(不同类型的动物发出不同的叫声),这样一来程序就具有了可扩展性。想让一只狗发出叫声时,只需增加一段代码即可,而不用去改动原有的makeSound函数:

    var makeSound = function( animal ){
        animal.sound();
    };
    var Duck = function(){};
    Duck.prototype.sound = function(){
        console.log( '嘎嘎嘎' );
    };
    var Chicken = function(){};
    Chicken.prototype.sound = function(){
        console.log( '咯咯咯' );
    };
    makeSound( new Duck() ); // 嘎嘎嘎
    makeSound( new Chicken() ); // 咯咯咯
    /********* 增加动物狗,不用改动原有的makeSound 函数 ****************/
    var Dog = function(){};
    Dog.prototype.sound = function(){
        console.log( '汪汪汪' );
    };
    makeSound( new Dog() ); // 汪汪汪

      遵守开放——封闭原则的规律,最明显的就是找出程序中将要发生变化的地方,然后把变化封装起来。通过封装变化的方式,可以把系统中稳定不变的部分和容易变化的部分隔离开来。在系统的演变过程中,只需要替换那些容易变化的部分,如果这些部分是已经被封装好的,那么替换起来也相对容易。而变化部分之外的就是稳定的部分。在系统的演变过程中,稳定的部分是不需要改变的

      由于每种动物的叫声都不同,所以动物具体怎么叫是可变的,于是把动物具体怎么叫的逻辑从makeSound函数中分离出来。而动物都会叫这是不变的,makeSound函数里的实现逻辑只跟动物都会叫有关,这样一来,makeSound就成了一个稳定和封闭的函数。除了利用对象的多态性之外,还有其他方式可以帮助编写遵守开放——封闭原则的代码

    【放置挂钩】

      放置挂钩(hook)也是分离变化的一种方式。在程序有可能发生变化的地方放置一个挂钩,挂钩的返回结果决定了程序的下一步走向。这样一来,原本的代码执行路径上就出现了一个分叉路口,程序未来的执行方向被预埋下多种可能性。

      由于子类的数量是无限制的,总会有一些“个性化”的子类迫使不得不去改变已经封装好的算法骨架。于是可以在父类中的某个容易变化的地方放置挂钩,挂钩的返回结果由具体子类决定。这样一来,程序就拥有了变化的可能

    【使用回调函数】

      在javascript中,函数可以作为参数传递给另外一个函数,这是高阶函数的意义之一。在这种情况下,通常会把这个函数称为回调函数。在javascript版本的设计模式中,策略模式和命令模式等都可以用回调函数轻松实现

      回调函数是一种特殊的挂钩。可以把一部分易于变化的逻辑封装在回调函数里,然后把回调函数当作参数传入一个稳定和封闭的函数中。当回调函数被执行的时候,程序就可以因为回调函数的内部逻辑不同,而产生不同的结果

      比如,通过ajax异步请求用户信息之后要做一些事情,请求用户信息的过程是不变的,而获取到用户信息之后要做什么事情,则是可能变化的:

    var getUserInfo = function( callback ){
        $.ajax( 'http:// xxx.com/getUserInfo', callback );
    };
    getUserInfo( function( data ){
        console.log( data.userName );
    });
    getUserInfo( function( data ){
        console.log( data.userId );
    });

      另外一个例子是关于Array.prototype.map的。在不支持Array.prototype.map的浏览器中,可以简单地模拟实现一个map函数

      arrayMap函数的作用是把一个数组“映射”为另外一个数组。映射的步骤是不变的,而映射的规则是可变的,于是把这部分规则放在回调函数中,传入arrayMap函数:

    var arrayMap = function( ary, callback ){
        var i = 0,
        length = ary.length,
        value,
        ret = [];
        for ( ; i < length; i++ ){
            value = callback( i, ary[ i ] );
            ret.push( value );
        }
        return ret;
    }
    var a = arrayMap( [ 1, 2, 3 ], function( i, n ){
        return n * 2;
    });
    var b = arrayMap( [ 1, 2, 3 ], function( i, n ){
        return n * 3;
    });
    
    console.log( a ); // 输出:[ 2, 4, 6 ]
    console.log( b ); // 输出:[ 3, 6, 9 ]

      有一种说法是,设计模式就是给做的好的设计取个名字。几乎所有的设计模式都是遵守开放——封闭原则的。不管是具体的各种设计模式,还是更抽象的面向对象设计原则,比如单一职责原则、最少知识原则、依赖倒置原则等,都是为了让程序遵守开放——封闭原则而出现的。可以这样说,开放——封闭原则是编写一个好程序的目标,其他设计原则都是达到这个目标的过程

    【发布——订阅模式】

      发布——订阅模式用来降低多个对象之间的依赖关系,它可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。当有新的订阅者出现时,发布者的代码不需要进行任何修改;同样当发布者需要改变时,也不会影响到之前的订阅者

    【模板方法模式】

      模板方法模式是一种典型的通过封装变化来提高系统扩展性的设计模式。在一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以把这部分逻辑抽出来放到父类的模板方法里面;而子类的方法具体怎么实现则是可变的,于是把这部分变化的逻辑封装到子类中。通过增加新的子类,便能给系统增加新的功能,并不需要改动抽象父类以及其他的子类,这也是符合开放——封闭原则的

    【策略模式】

      策略模式和模板方法模式是一对竞争者。在大多数情况下,它们可以相互替换使用。模板方法模式基于继承的思想,而策略模式则偏重于组合和委托。策略模式将各种算法都封装成单独的策略类,这些策略类可以被交换使用。策略和使用策略的客户代码可以分别独立进行修改而互不影响。增加一个新的策略类也非常方便,完全不用修改之前的代码

    【代理模式】

      拿预加载图片举例,现在已有一个给图片设置src的函数myImage,想为它增加图片预加载功能时,一种做法是改动myImage函数内部的代码,更好的做法是提供一个代理函数proxyMyImage,代理函数负责图片预加载,在图片预加载完成之后,再将请求转交给原来的myImage函数,myImage在这个过程中不需要任何改动。预加载图片的功能和给图片设置src的功能被隔离在两个函数里,它们可以单独改变而互不影响。myImage不知晓代理的存在,它可以继续专注于自己的职责——给图片设置src

    【职责链模式】

      把一个巨大的订单函数分别拆成了500元订单、200元订单以及普通订单的3个函数。这3个函数通过职责链连接在一起,客户的请求会在这条链条里面依次传递:

    var order500yuan = new Chain(function( orderType, pay, stock ){
    // 具体代码略
    });
    
    var order200yuan = new Chain(function( orderType, pay, stock ){
    // 具体代码略
    });
    
    var orderNormal = new Chain(function( orderType, pay, stock ){
    // 具体代码略
    });
    
    order500yuan.setNextSuccessor( order200yuan ).setNextSuccessor( orderNormal ); 
    order500yuan.passRequest( 1, true, 10 );    // 500 元定金预购,得到 100 优惠券

      可以看到,当增加一个新类型的订单函数时,不需要改动原有的订单函数代码,只需要在链条中增加一个新的节点

      在职责链模式代码中,开放——封闭原则要求只能通过增加源代码的方式扩展程序的功能,而不允许修改源代码。那往职责链中增加一个新的100元订单函数节点时,不也必须改动设置链条的代码吗?代码如下:

    order500yuan.setNextSuccessor(order200yuan).setNextSuccessor(orderNormal);

      变为:

    order500yuan.setNextSuccessor(order200yuan).setNextSuccessor(order100yuan).setNextSuccessor(orderNormal);

      实际上,让程序保持完全封闭是不容易做到的。就算技术上做得到,也需要花费太多的时间和精力。而且让程序符合开放——封闭原则的代价是引入更多的抽象层次,更多的抽象有可能会增大代码的复杂度。更何况,有一些代码是无论如何也不能完全封闭的,总会存在一些无法对其封闭的变化

      作为程序员,可以做到的有下面两点

      1、挑选出最容易发生变化的地方,然后构造抽象来封闭这些变化

      2、在不可避免发生修改的时候,尽量修改那些相对容易修改的地方。拿一个开源库来说,修改它提供的配置文件,总比修改它的源代码来得简单

     

    转载于:https://www.cnblogs.com/xiaohuochai/p/8050983.html

    展开全文
  • 设计原则

    2020-07-14 13:23:14
    单一职责原则提出了一个编写程序标准,用“职责”或“变化原因”来衡量接口或类设计是否优良,但“职责”和“变化原因”都是可度量,因项目而异,因环境而异。 里氏替换原则(LSP): 里氏替换要求凡是使用...
  • 设计模式原则

    2016-09-06 22:00:00
    在面向对象开发中,我们进行代码设计时需要遵循设计模式基本原则。遵循这些设计原则可以有效提高系统复用性、可扩展性和可维护性。 常用设计原则包括: 单一职责原则:类职责要单一,能将太多职责...
  • SOLIDSOLID并是一个设计原则,而是五个设计原则的合称;1.S - 单一职责Single Responsibility Principle:单一职责原则一个类、一个模块或者是一个方法职责应该是单一;如果同时承担了两个责任,那么当其中一...
  • 软件测试应遵循一些基本原则: 1)尽早地,不断地进行软件测试,把...5)测试实例设计,应包括有效和期望输入条件以及无效期望输入条件; 6)检查程序应完成任务外,并且检查程序是否做了它应该做...
  • 程序设计的SOLID原则

    2015-11-03 22:06:00
    要想设计一个良好程序,建议采用SOLID原则,若考虑了SOLID,可以使程序在模块内具有高内聚、而模块间具有低耦合特点。 SOLID原则包括5方面内容: S---单责任原则(SRP) 一个模块只做一件事,责任单一,以便...
  • 面向对象7大设计原则

    千次阅读 2018-08-04 19:37:01
    单一职责原则是实现高内聚,低耦合的指导方针,是最简单又最难以运用的原则。 开闭原则 软件实体对于扩展开放,对于修改是关闭的(软件实体应该尽量在修改原有代码的基础上面进行扩展)。实现开闭原则的关键是...
  • 资源匮乏,包括人力和时间,大家总是处在紧急出活状态,客观上没有条件去做充分的设计代码实现 即便是真正想追求这个团队也是如此 结果间接性:糟糕的设计代码实现它结果非常隐晦,往往在很...
  • 面向对象的设计原则,可以说每种设计模式都是为了让代码迎合其中一个或多个原则而出现, 它们本身已经融入了设计模式之中,给面向对象编程指明了方向。适合javascript开发的设计原则包括是单一职责原则、最少知识...
  • 里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数...
  • 经典的设计原则,其中包括,SOLID、KISS、YAGNI、DRY、LOD 等 单一职责原则(SRP, Single Responsibility Principle) SOLID 原则第一个原则:单一职责原则。 单一职责原则的定义描述非常简单,也难理解。一...
  • 云架构设计原则

    2019-03-06 12:59:59
    技术人员部署服务器、管理服务器模板、更新服务器和定义基础设施模式都是通过代码来完成,并且是自动化能通过手工安装或克隆方式来管理服务器资源,运维人员和开发人员一起以资源配置应用代码为中心,...
  • 在进行软件设计阶段,或者说架构设计阶段,遵循一些设计原则能够搞软件可读性、复用性、可维护性。在讨论设计模式之前,有必要先掌握设计原则的相关知识,它可以作为评价一个设计是否良好尺度。本质上,我们所说...
  • 开放封闭原则,它规定了软件设计和维护时应该遵守一种规则,即对扩展开放、对修改关闭。 扩展和修改难理解,扩展指是对原有结构进行拓展,增加新类让其继承父类、实现接口,利用多态、继承等特性进而实现...
  • 程序设计技术发展至今,对象概念始终贯穿与设计过程...人们常说美食要色香味俱全,代码例外,这里首先总结了设计中最重要,也最容易为人所忽略几个原则,这几个点都是描述代码的外观特点,我称之为"代码...
  • code设计原则

    2017-08-09 13:26:23
    1.单一职责原则:一个对象(方法)只做一件事情。“职责”:“引起变化原因”。2.最少知识原则:一个...当需要改变一个程序功能或者给这个程序增加新功能时候,可以使用增加代码的方式,允许修改程序源代码
  • 原则和单一职责原则都要求设计者对业务进行拆分处理,最终达到低耦合高内聚的设计目标。两者区别在于: 约束对象不同。显而易见,接口隔离原则是针对接口层面,包括接口和抽象类,在构建低层基础框架过程中
  • Java的设计六大原则

    2015-07-13 17:25:10
    一个庞大对象承担太多责任,当客户端需要该对象某一职责时,就不得将所有职责包括进来,从而造成冗余的代码。类职责越少,则对象之间依赖关系越少,耦合度就减弱,受其他对象约束与牵制就越少,从而...
  • 设计原则之开闭原则

    2020-09-05 18:15:28
    定义: Software entities like classes,modules and functions should be open for extensions but closed for ...意思就是软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。 软件实体包括.
  • 开闭原则(Open Closed Principle,OCP) 一、定义:软件实体应当对扩展开放,对修改关闭;...开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。具
  • 开闭原则允许软件实体在更改其代码的情况下变更其行为(变更包括改变和增加)。 定义 开闭原则(Open Close Principle) 是面向对象设计中重要的原则,它要求软件实体对扩展开放,对修改封闭,软件实体包含函数、类....

空空如也

空空如也

1 2 3 4 5 ... 19
收藏数 367
精华内容 146
关键字:

代码设计的原则不包括