走向灵活的软件之路--面向对象六大基本原则

六大基本原则

原文地址


在我自己的学习历程当中,一开始接触到java语言,以及面向对象的学习,然后紧接着开始接触android在相当多长的一段时间一直钻研与基础知识理论内容的学习。在经过了一段时间的代码编写之后,总会多多少少的出现一些避免不了的问题,一开始以为是基础知识的不牢固,到后来才知道代码规范,原来是代码编写当中必不可少,也是避免出先Bug的重要环节,而代码规范所涉及到的一项知识就是设计原则,下面根据我所了解的知识和大家一起来探讨设计原则。如有不对的地方还请大家提出。


一.优化代码—单一职责原则

单一职责原则,英文名又称SRP(Single Responsibility Principle),就一个类而言,应该仅有一个引起他变化的原因,简单来说,一个类中应该是一组相关性很高的函数数据的封装。

可以这么来说,通俗的来说单一职责原则就是一个人只做一件事情,比如你自己开了一家饭店,你自己是老板,是厨师,也是服务员,这就不满足单一职责原则,这种情况当你的顾客越来越多的时候你一个人往往就忙不过来,那么就需要增加员工,不然就会导致利润的流失,当增加到一定程度的时候,饭店里有了营业员,厨师,等等每一个人只负责一种职务那这就是单一职责原则了。

单一职责原则的代码示例

比如我们要写一个缓存工具类,用来缓存从网络上加载的数据,我们就可以分别建立三个类来声明不同存储介质的缓存文件,每个类只负责他本身的作用。

class DiskCache{
        //硬盘缓存
    }

class MemoryCache{
        //内存缓存
    }

class NetCache{
        //网络缓存
    }

二.让程序更稳定,更灵活—开闭原则

开闭原则的英文全称是:(Open Close Principle),缩写成OCP,他是java里面的最基础的设计原则,他知道我们如何建立一个稳定的,灵活的系统;

定义:软件中的对象(类,模板,函数等)应该对于扩展开放的,但是对于修改是封闭的,也就是说,在软件的需求发生变化的时候,我们应该使用扩展的方式来实现变化,而不是修改原有的代码,这样很容易出现错误,
在软件的开发中,最不会变化的就是变化本身,也就是说,无论是软件的升级,维护,修改,更新,任何操作、,只要是这个软件处在使用中,那么代码就会不断地改变,所以代码是无时无刻都在改变,所以我们更应该让代码满足开闭原则,这样才能更好的解决改变时所产生的负担

开闭原则的代码示例

//1.换粗你的接口
interface ImageLoad{
        void put(Bitmap bitmap,String url);
        Bitmap get();
    }

//硬盘缓存
    class DiskCache implements ImageLoad{

        @Override
        public void put(Bitmap bitmap, String url) {
        }
        @Override
        public Bitmap get() {
            return null;
        }
    }
    //缓存工具类
    class BitmapUtils{

        public void setCache(ImageLoad imageLoda){

        };
    }

上述代码就是满足开闭原则呢。为什么呢?我们来看一下,我们有一个硬盘缓存类,如果我们想增加其他的缓存方式,比如内存缓存,我们只需要这样做,代码如下

//新建一个缓存类
   class MemoryCache implements ImageLoad{

        @Override
        public void put(Bitmap bitmap, String url) {

        }

        @Override
        public Bitmap get() {
            return null;
        }
    }
    //新建缓存类,病通过工具类以set的形式设置就可以,
new BitmapUtils().setCache(new MemoryCache());

上述代码是不是就是对扩展开放了呢?对修改关闭了呢?答案是肯定的,我们只需要新建一个类,在使用的时候,直接吧新建的缓存类添加到utils类中就可以。

三.构建更好扩展性的系统—-里氏代换原则

英文全程是:Liskov Substitution Principle .缩写:LSP

定义:如果对每一个类型为S的对象O1,都有一个类型为T的O2,使得以T定义的所有程序P在所有的O1都替换成O2时,程序P的行为没有发生变化,那么类型S就是类型T的子类型,
简单点说就是:面向对象的语言的三大特点是继承,封装,多态,里氏代换原则就是利用继承和多台两大特性。简单点来说,就是任何引用其基类的地方,都能透明的使用其自雷的对象,反过来就不行了,
例如:如下面的代码所示

//people类students的父类
class people{}
//students类,集成people
class students extends people{}
//工具类,有两个方法
class Tools{

public void show(people p){}

public void show(students s){}

}

如上图所示,一个类 people类作为父类,一个students类,作为子类,在Tools工具类中show()函数中的
show(people p)可以传入people和students对象的实例
show(students s)只可以传入students的实例,
这就是上面所说的,父类能够出现的地方子类一定能够出现,但是反过来就不满足了

    根据上面可以知道,里氏代换原则的核心是抽象,抽象又依赖于继承这个特性,在OOP中,继承的优缺点都相当的明显,

优点:
1.代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性
2.子类与父类基本相似,但是又与父类有所区别
3.提高代码的可扩展性
缺点: 
1.继承是侵入性的,只要继承必须拥有父类的所有的属性和方法。
2.可以造成子类代码的冗余,灵活性降低,因为子类必须拥有父类的方法和属性

四.让项目拥有变化的能力—-依赖倒置原则

依赖倒置原则的英文全称是:Dependence Inversion Principle 缩写是:DIP

    以来倒置原则指代了一种特定的解耦形式,使得高层次的模块不依赖于底层次的模块二实现细节的目的,依赖模块被颠倒了,

    1.高层模块不应该依赖于底层模块,两者都应该依赖于抽象
    2.抽象不应该依赖细节,
    3.细节应该依赖抽象

    在java语言中,抽象就是指接口或抽象类,两者都是不可以直接被实例化的,细节就是实现类,实现接口或继承抽象类而产生的类就是细节,
    模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系通过接口或者抽象类产生。如下实例
//抽象类接口
interface ImageLoad{}

class MemoryCache implement ImageLoad{}

//BitmapUtils类

class BitmapUtils{
//1.第一种实现的方法
public void setCache(MemeoryCache cache){}
//2.第二种实现的方法
public void setCache(ImageLoad cache){}
}
    按照上述伪代码所示,在BitmapUtils中,可以了解到。他的方法中,
    第一种:依赖于细节,也就是依赖MemoryCache类,
    第二种:依赖于抽象,

按照上面的方法,将方法依赖于抽象,而不是具体的实现,这样当需求需要发生改变的时候,我们只需要让缓存继承与ImageLoad或者继承与他的子类就可以了,这样看来就是更灵活的手段。

五.让系统具有更高的灵活性—–接口隔离原则

英文全称:InterfaceSegregation Principles。缩写ISP

    定义:客户端不应该依赖他不需要的接口
    定义二:类间的依赖关系应该建立在最小的接口上,接口隔离原则将非常强庞大,臃肿的接口拆分成更小的和更具体的接口,这样客户端只需要知道他们感兴趣的方法,接口隔离原则是系统解开耦合,从而容易重构,更改和重新部署。
    interface people{
        void eat();
        void run();
        void drink();
        void smoke(); 
    }

    class students implements people{

        @Override
        public void eat() {

        }

        @Override
        public void run() {

        }

        @Override
        public void drink() {

        }

        @Override
        public void smoke() {

        }
    }
    如上面伪代码所展现的一样,如果我们有一个people的接口,如果我们现在需要一个学生类,那么如果直接继承与people,就像上述一样,肯定是不行的,像一些方法,比如smoke,我们不应该暴露给学生类,所以我们就要使用接口隔离,重新定义学生类的接口。

注意:如果像上述这样实现,还有一个问题,就是把不需要的接口暴露出来,这样就会造成许多安全问题。
下面再来看一个实例  
        FileInputStream is  = null;
            try {
                is = new FileInputStream("");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }finally {
                if(is != null){

                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    上述只是一个文件流的关闭,可读性是非常差的,而且如果一个程序中有很多的流,那么就需要使用不知道多少个finally然后处理操作,关闭。显然这是非常不实用的。我们就需要更改一下
  public static final class CloseUtils{
        public static void CloseQuietly(Closeable closeable){
            if(closeable != null){
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
//如果需要关闭流只需要这样做
CloseUtils.CloseQuietly(is);
    这样看来,代码就简洁了很多了。

更好的扩展性—–迪米特原则

迪米特原则的英文全程是:Law of Demeter 缩写:LOD
也称为:最少知道原则,:Least Knowledge Principle

    定义:一个对象应该对其他对象有最少的了解,通俗的讲就是,一个类应该对自己需要或者耦合或调用的类知道的最少,类的内部结构如何实现与调用者或者依赖者没有关系,调用者或者依赖者只需要知道他需要的方法即可,其他的一概不用管,类与类之间的关系越密切,耦合度就越大当一个类发生改变时,对另一个类的影响也就越大。

    定义:它还有一个英文的解释就是:Only talk to your friends  翻译过来就是:只与直接的朋友接近,什么叫做直接的朋友呢,每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多,如组合,聚合,依赖等。
我们来看一个实例
//房间类
public class Room{
    public float area;
    public float price;
}
//具体的房间
public class Room1 extends Room{}
public class Room2 extends Room{}
public class Room3 extends Room{}
public class Room4 extends Room{}

//租户类
public class Tenant{
    public float area;
    public float price;

    private boolean isSuitable(Room room){}
}

按照上述的代码可知,如果租户想要租到自己想要的房子,就必须首先获取每个Room的实例,然后依次调用isSuitable(new Room1())方法,逐个判断是否满足自己的要求。这样是不是显得太麻烦,我只是想租一间符合自己条件的房子,却需要了解这么多,如果有十个租户需要租房子,那么每个租户都需要重复这种操作,这就显得太复杂了,所以我们可以引入一个中间类,当我们想要租房子的时候只需要告诉他我们的要求,然后返回符合条件的房子就可以了,具体实现:

public class Mediator{

public List<Room> mrooms = new ArrayList<Room>();

    public List<Room> getAllRooms(){
        return mrooms;
    }
    public List<Room> getSuitableRooms(float area,float price){
        return mrooms;
    }
}


public class Tenant{
    public float area;
    public float price;
public void rentRoom(Mediator m){
    List<Room> mroons = m.getSuitableRooms(area,price);
}

从上述中间类的代码中可以明白,租户只需要调用getAllRoom();或者getSuitableRooms(float area,float price);就可以获得需要的房间,这样就不需要去了解每个房子了,这样可以再很大程度上节省了时间,也降低了耦合度,

设计模式是编程所不可缺少知识,其实理解这些知识并不是难事,而难的是在理解的基础上做到代码上的灵活运用,只有最大程度的练习才能更好的做到融会贯通。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值