Java思想进阶之设计模式解析前篇(七大原则)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

设计模式,对于刚入门的程序员来说几乎和JVM是一个级别的,总觉得遥不可及。也觉得目前这个阶段没必要研究这样的知识。这种思想是错误的,不管你是刚入门的小白还是知识渊博的大牛,甚至其他行业的都可以学习设计模式,它不仅仅能应用于软件工程,也能应用于生活中的方方面面设计。起初设计模式是被应用于建筑领域的设计中,后来才慢慢引用于软件设计领域。
软件设计模式,是对软件设计中普遍存在(反复出现)的各种问题,所提出来的解决方案。但是它不仅仅局限于是方案,更是编码的规范以及思维。毫不夸张的说,它也是历代程序员代码设计经验的总结。其目的为了提高代码的可重用性、代码的可读性以及代码的可靠性

我们不要做码农,要做有思维、有灵魂的软件工程师。


一、七大原则

使用23种设计模式的基础就是遵守七大原则,下文会逐个介绍。另外由于时间精力有限,23种设计模式不会逐个研究,只会介绍15种常用的、重要的设计模式。初中级开发,能掌握个15种设计模式,应该差不多了。当然,掌握越多越好。

1、开闭原则

开闭原则是编程种最基础也是最重要的设计原则,其他六种原则其实最终就是为了实现开闭原则。
官方解释:软件实体应当对扩展开放,对修改关闭。

一个软件实体如类、模块或函数应该对扩展开放,对修改关闭,用抽象构建框架,用实现扩展细节,当软件需要变化是,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码类实现改变。

开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。

黄金法则:当问到什么设计模式或者七大原则有啥优点的时候,记住 耦合性,内聚性,可维护性、可读性、可扩展性、重用性、灵活性等多方面去思考解答。

2、单一职责原则

官方解释:就一个类而言,应该仅有一个引起它变化的原因,应该只有一个职责

皇家翻译:就一个后台开发人员而言,应该仅有后台出问题这一个原因才能使他加班,前端炸了或者运维方面出问题了,他照样下班。分工明确,后台开发只负责后台开发职责。
如果不遵守单一职责原则,那么代码的耦合性很高,内聚性低。

优点:降低类的复杂性,提高类的可读性以及可维护性。

3、接口隔离原则

官方定义:客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。

当普通类实现一个接口时, 重写其下所有方法,这些方法真的有必要一一重写吗?如果不需要每个方法都重写,那么应该考虑把这个接口拆分成多个小接口,实行接口隔离原则。
优点:降低系统耦合性,提高内聚性、灵活性以及可维护性。当类实现接口时,可避免一些被迫实现一些与该类无关的代码,这和前面的单一原则有些类似,都是为了提高类的内聚性,降低他们之间的耦合性。但是单一职责注重的是类的职责,而接口隔离原则注重的对接口依赖的隔离。
上段代码理解理解,很简单代码,但需要仔细看。

package com.principle;

public class Segregation {

    public static void main(String[] args) {
//某粉丝 要看cxk跳、唱、篮球,不看rap和演戏
         Fans fans=new Fans();
         fans.useMoneyLookSing(new CXK());
         fans.useMoneyLookDance(new CXK());
         fans.useMoneyLookPalyBasketball(new CXK());
//某王老五 要看LH看rap、跳、演戏,不看唱、篮球
         WLW wlw = new WLW();
         wlw.useMoneyLookActing(new LH());
         wlw.useMoneyLookRap(new LH());
         wlw.useMoneyLookDance(new LH());


    }

}

//假设这是一个公共接口 实现了五个方法 唱、跳、rap、打篮球、演戏
interface showTime{
    void sing();
    void dance();
    void rap();
    void basketball();
    void acting();
}

//CXK类实现了技能接口
class  CXK implements showTime{
    @Override
    public void sing() { System.out.println("坤坤唱歌"); }
    @Override
    public void dance() { System.out.println("坤坤跳舞"); }
    @Override
    public void rap() { System.out.println("坤坤rap"); }
    @Override
    public void basketball() { System.out.println("坤坤杂技篮球"); }
    @Override
    public void acting() { System.out.println("坤坤演戏"); }
}
//LH类实现了公共接口
class  LH implements showTime{
    @Override
    public void sing() { System.out.println("鹿鹿唱歌"); }
    @Override
    public void dance() { System.out.println("鹿鹿跳舞"); }
    @Override
    public void rap() { System.out.println("鹿鹿rap"); }
    @Override
    public void basketball() { System.out.println("鹿鹿杂技篮球"); }
    @Override
    public void acting() { System.out.println("鹿鹿演戏"); }
}

//某粉丝 要看cxk跳、唱、篮球,不看rap和演戏
class Fans {
      public void useMoneyLookSing(showTime i){
          i.sing();
      }
      public void useMoneyLookDance(showTime i){
        i.dance();
      }
      public void useMoneyLookPalyBasketball(showTime i){
        i.basketball();
     }

}

//某王老五 要看LH看rap、跳、演戏,不看唱、篮球
class WLW {
    public void useMoneyLookDance(showTime i){
        i.dance();
    }
    public void useMoneyLookRap(showTime i){
        i.rap();
    }
    public void useMoneyLookActing(showTime i){
        i.acting();
    }
}


以上代码,在王老五或者粉丝花钱要看娱乐时,俩个实现类都实现了showtime这个接口下的所有方法,但是这俩个实现类并不是都能实现唱跳rap打篮球以及演戏,这是典型的违反了接口隔离原则的代码。所以解决方案是把接口细粒化,可以拆成多个接口,需要用到哪些技能就继承哪个接口,很简单,就不多写了,以免律师函警告。

4、依赖倒转原则

官方解释:
高层模块不应该依赖低层模块,两者都应该依赖其抽象;
抽象不应该依赖细节,细节应该依赖抽象
其核心思想是:要面向接口编程,不要面向实现编程。
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
对于这些官方术语, 乍一看是懵的。但是你仔细一看就会发现,还不如乍一看。
直接上代码

package com.principle;

public class DependencyReverse {

    public static void main(String[] args) {
        Person person = new Person();
        person.eat(new Meat());
        person.eat(new Fruits());
    }

}
//定义食物接口
interface IFood {
    public String eat();
}
//茶
class Fruits implements IFood {
    public String eat() {
        return "水果";
    }
}
//汤
class soup implements IFood {
    public String eat() {
        return "汤";
    }
}
//肉
class Meat implements IFood {
    public String eat() {
        return "肉";
    }
}




class Person {
    //依赖倒置  面向接口编程 写法
    public void eat(IFood food ) {
        System.out.println(food.eat());
    }
    //普通 面向实现具体 写法
    public  void  eat(Meat meat){
        System.out.println(meat.eat());
    }
}

上面这个代码写的客户吃东西的案例,普通写法是没有接口的,后面要写依赖倒置原则所以也加上了,在上面可以看到,依赖倒置写法使用接口来制定好规范和契约,把展现的细节交给他们的实现类去完成,这就实现了,抽象不应该依赖细节,细节应该依赖抽象。也体现了要面向接口编程,不要面向实现编程的核心思想。

5、里氏替换原则

麻省理工学院一位叫做里斯科夫的女士提出来的,这是对继承更深的理解。
她提出:继承必须确保超类所拥有的性质在子类中仍然成立
翻译过来就是,子类继承父类,父类所拥有的性质仍然存在。也就是说子类可以扩展父类的功能,但是不能修改父类原有的功能。如果子类把父类的方法都重写了,那继承又有什么用呢?如果非得重写父类的方法,可以让父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替才是符合里氏替换原则。
优点:加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性。

package com.principle;

public class Liskov {

    public static void main(String[] args) {

        son1 son1 = new son1();
        son1.sing();
        son1.sing1();

    }
}

class father {
    void sing() {
        System.out.println("精通粤语、英语等各种年代的歌");
    }
    void  work(){
        System.out.println("精通Java、PHP、go等流行语言");
    }
    void Hair(){
        System.out.println("十年程序员标准发量,地中海可还行");
    }

}
class son extends father{
    //除了father的技能外 还会跳舞
    void dance(){
        System.out.println("练习俩年半的篮球杂技舞蹈");
    }
    //修改了father的良好基因
    void sing(){
        System.out.println("不好意思,我只会rap了");

    }

}
//这个时候我们要son唱歌,他肯定开口就是哟哟哟切克闹,因为他重写了father的方法,不符合里氏替换规则 因此做以下修过
//让父类和子类都继承一个更通俗的基类 去除原来的继承关系 采用依赖、聚合、组合等关系代替才是符合里氏替换原则
//基类
class base{

}
//父类
class  father1 extends base{
    void sing() {
        System.out.println("精通粤语、英语等各种年代的歌");
    }

}
//子类
class son1 extends  base{
    //组合关系 不再是父子 而是兄弟搭档 son1需要自己的rap也需要兄弟的sing技能
    private father1 father1= new father1();


    //son1的rap
    void sing(){
        System.out.println("不好意思,我只会rap了");

    }
    //father1的sing
    void sing1(){
        this.father1.sing();
    }
}



在日常开发中,我们重写父类的方法很常见。但是里氏替换原则是一个标准,或者说是一种规范。我们应该向这个原则靠,里氏替换原则也是实现开闭原则的重要方式之一。

6、合成复用原则

合成复用原则:它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
因为继承的耦合性很高,不利于类的扩展和维护。如果要使用继承,就得遵守里氏替换原则。能用组合聚合关系尽量用组合聚合。因为这个原则比较简单,而且上个原则也写到过它的代码。所以这里就不在啰嗦,但是这个原则和开闭原则一样,是相当重要的。
合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。

7、迪米特法则

官方定义:Talk only to your immediate friends and not to strangers
只与你的直接朋友交谈,不跟“陌生人”说话,也叫做最少知道原则。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
这里引用网上代码做为示例
明星与经纪人的关系实例。
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则

package principle;

public class LoDtest {
    public static void main(String[] args) {
        Agent agent = new Agent();
        agent.setStar(new Star("林心如"));
        agent.setFans(new Fans("粉丝韩丞"));
        agent.setCompany(new Company("中国传媒有限公司"));
        agent.meeting();
        agent.business();
    }
}

//经纪人
class Agent {
    private Star myStar;
    private Fans myFans;
    private Company myCompany;

    public void setStar(Star myStar) {
        this.myStar = myStar;
    }

    public void setFans(Fans myFans) {
        this.myFans = myFans;
    }

    public void setCompany(Company myCompany) {
        this.myCompany = myCompany;
    }

    public void meeting() {
        System.out.println(myFans.getName() + "与明星" + myStar.getName() + "见面了。");
    }

    public void business() {
        System.out.println(myCompany.getName() + "与明星" + myStar.getName() + "洽淡业务。");
    }
}

//明星
class Star {
    private String name;

    Star(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

//粉丝
class Fans {
    private String name;

    Fans(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

//媒体公司
class Company {
    private String name;

    Company(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

总结

以上原则的核心思想主要是

  • 考虑到可能变化的部分,使其独立,变化的代码尽可能不影响其他代码。
  • 针对接口编程,而不是针对实现编程。重在抽象设计,而不是具体实现细节。
  • 从耦合性,内聚性,可维护性、可读性、可扩展性、重用性、灵活性等多方面去思考。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值