初识软件架构设计原则


title: 初识软件架构设计原则
date: 2020-11-26 13:40:31
tags: 软件架构 java


软件架构设计原则

​ 设计原则是设计模式的基础,在实际开发中,并不是要求所有代码都遵循设计原则,我们要根据实际情况(例如人力、时间、成本一集质量),不能一味的追求完美,否则就是家中负担了。我们需要在合适的场景下面去遵循设计原则,达到一种平衡取舍,这样的话就可以帮我们设计出更加优雅的代码结构。

  • 开闭原则

    开闭原则是指一个软件实体应该对扩展开放,对修改关闭。用抽象构造框架,用扩展实现细节。例如许多公司规定每天上班八小时,但是不规定几点到,几点走。只要每天上够八小时就可以。下面以蛋糕售卖为例子进行介绍。

    1. 首先创建一个蛋糕的基本信息的接口ICake:

      public interface ICake {
          Double getPrice();
          String getName();
      }
      
    2. 由于蛋糕有很多的口味(草莓,巧克力,奶油等),这里我们选择一个苹果口味的蛋糕做例子(这个口味有点怪哈,哈哈),创建一个苹果味蛋糕的类:

      public class AppleCake implements ICake{
          private Double price;
          private String name;
      
          public AppleCake(Double price, String name) {
              this.price = price;
              this.name = name;
          }
      
          @Override
          public Double getPrice() {
              return this.price;
          }
      
          @Override
          public String getName() {
              return this.name;
          }
      }
      
    3. 现在我们苹果味的蛋糕可以正常售卖了,我们知道了它是苹果味的蛋糕和价钱,而不是草莓味的。但是,我们这个连锁店有一家店铺由于地理位置不好,只能打折销售,怎么办呢??改代码?但是其他店现在是不需要的啊,或许以后有需要。而且其他代码的逻辑都已经写好了,改的话万一有bug怎么办?所以我们就需要在不动原来代码的基础上,在增加一个苹果蛋糕打折的类就好了。如下:

      public class AppleDiscountCake extends AppleCake{
          public AppleDiscountCake(Double price, String name) {
              super(price, name);
          }
      
          /**
           * 苹果味的蛋糕原价钱
           * @return
           */
          public Double getOriginPrice() {
              return super.getPrice();
          }
      
          /**
           * 苹果味的蛋糕打折后的价钱
           * @return
           */
          public Double getPrice() {
              return super.getPrice() * 0.65;
          }
      }
      

      这样就可以解决问题了。

  • 依赖倒置原则

    依赖倒置原则是指设计代码结构时,上层模块不能依赖下层模块,二者都应该依赖抽象模块。抽象不能依赖细节,细节去依赖抽象,达到减少累与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性。我们还以蛋糕为例作味介绍。

    1. shmilylyp最近非常尝蛋糕,先买了一块苹果味的蛋糕,然后又买了一块草莓味的蛋糕。代码如下:

      public class Shmilylyp {
          public void buyAppleCake() {
              System.out.println("shmilylyp买了一个苹果味的蛋糕");
          }
      
          public void buyStrawberryCake() {
              System.out.println("shmilylyp买了一个草莓味的蛋糕");
          }
      
          public static void main(String[] args) {
              Shmilylyp shmilylyp = new Shmilylyp();
              shmilylyp.buyAppleCake();
              shmilylyp.buyStrawberryCake();
          }
      }
      

      shmilylyp这两块蛋糕吃的非常爽。

    2. shmilylyp吃完了两块蛋糕发现还想尝尝巧克力蛋糕怎么办呢??在写一个方法??但是如果还想吃其他的好多种类呢??继续写?不怕胖的哈。这样的话我们就需要不停的修改main方法中的代码,而且也需要修改上层也就是Shmilylyp这个吃货中的代码。这会造成代码的不稳定的。在说shmilylyp要吃那种还的人家店主去做那种,又不是他家开的。所以我们就需要优化代码了。优化后的代码如下:

      public class Shmilylyp {
          private ICake cake;
      
          public void setCake(ICake cake) {
              this.cake = cake;
          }
      
          public void buyCake() {
              cake.buyCase();
          }
      
          public static void main(String[] args) {
              Shmilylyp shmilylyp = new Shmilylyp();
              shmilylyp.setCake(new StrawberryCake());
              shmilylyp.buyCake();
              shmilylyp.setCake(new AppleCake());
              shmilylyp.buyCake();
          }
      }
      
      public interface ICake {
          void buyCase();
      }
      
      public class AppleCake implements ICake {
          @Override
          public void buyCase() {
              System.out.println("shmilylyp 买了一个苹果蛋糕");
          }
      }
      
      public class StrawberryCake implements ICake{
          @Override
          public void buyCase() {
              System.out.println("shmilylyp 购买了一个草莓味的蛋糕");
          }
      }
      

      这样的话,由店主提前把他能做的蛋糕做好拿来买(假设只做两种哈,后面在慢慢加的),shmilylyp就可以选择做好的进行购买了。这样店主不依靠shmilylyp想吃什么而去做什么样的蛋糕,shmilylyp只能去选择店主摆出来的蛋糕。也就是依赖倒置原则,高层模块不依赖低层模块,低层模块依赖高层模块。这样的雇佣才是买家与买家的关系,不是那种专门雇佣的了哈。

  • 单一职责原则

    单一原则顾名思义就是自己做自己份内的,擅长的事(有点偏哈)。指不要存在多于一个导致类变更的原因。假如我们有一个类负责两个事情,一旦其中一个事情发生了变更,那么有可能就会影响到另外一个,导致发生故障,这就违反了单一职责原则了。还是拿我们吃的蛋糕来说。

    1. 蛋糕和馒头的做法是有差别的,做法是不一样的。一个需要精心雕刻,贵一点,一个只需要简单蒸一下就好了。我们来看看代码是这样的
    public class PastryDemo1 {
    
        public void makePastry(String pastryName) {
            if ("蛋糕".equals(pastryName)) {
                System.out.println(pastryName + "是需要精加工的");
            } else {
                System.out.println(pastryName + "是比较简单的");
            }
        }
    
        public static void main(String[] args) {
            PastryDemo1 pastryDemo1 = new PastryDemo1();
            pastryDemo1.makePastry("蛋糕");
            pastryDemo1.makePastry("小馒头");
        }
    }
    

    但是,我们现在变了,蛋糕的种类多了,蛋糕需要加草莓了,馒头不需要。又或者馒头有其他的要求了,这样的话我们不只是需要变动馒头或者蛋糕一种,还需要动另外一种。这样就会容易出现问题。所以我们最好是把他两分开。

    1. 我们可以创建一个馒头的类和一个蛋糕的类,这样他们就可以自己随便改变而不需要影响另外一个类。代码如下:
    public class PastryDemo2 {
        public static void main(String[] args) {
            Cake cake = new Cake();
            cake.make();
            SmallBun smallBun = new SmallBun();
            smallBun.make();
        }
    }
    
    public class Cake {
        public void make() {
            System.out.println("做了一个精加工的草莓蛋糕");
        }
    }
    
    public class SmallBun {
        public void make() {
            System.out.println("做了一个比较简单的小馒头");
        }
    }
    
  • 接口隔离原则

    接口隔离原则是指用多个专门的借口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。一个类对另一类的依赖应该建立在最小的接口之上。同时应该建立单一饿接口,不要建立庞大臃肿的接口,尽量细化接口。

    这次我们不料吃的了,通过动物行为来进行分析。

    鸟和狗都属于动物,鸟有吃,飞的行为,狗有吃,看门的行为。我们看以下代码:

    public interface IAnimal {
        // 吃
        void eat();
        // 飞
        void fly();
        // 看门
        void gatekeeper();
    }
    
    public class Bird implements IAnimal{
        @Override
        public void eat() {
            System.out.println("我是一只鸟,我要吃东西");
        }
    
        @Override
        public void fly() {
            System.out.println("我是一只鸟,我可以飞");
        }
    
        @Override
        public void gatekeeper() {
    
        }
    }
    
    
    public class Dog implements IAnimal{
        @Override
        public void eat() {
            System.out.println("我是一只狗,我可以吃");
        }
    
        @Override
        public void fly() {
    
        }
    
        @Override
        public void gatekeeper() {
            System.out.println("我是一只狗,我可以看门");
        }
    }
    

    以上代码中,狗和鸟都有吃的行为,但是狗不能飞,鸟不能看门,这两个行为就只能空着了。这样设计当然是不可能的。所以我们可以根据不同的行为来设计接口,如下:

    public class Dog implements IGatekeeper, IEat{
        @Override
        public void eat() {
            System.out.println("我是一只狗,我可以吃");
        }
    
        @Override
        public void gatekeeper() {
            System.out.println("我是一只狗,我可以看门");
        }
    }
    
    public class Bird implements IEat, IFly{
    
        @Override
        public void eat() {
            System.out.println("我是一只鸟,可以吃");
        }
    
        @Override
        public void fly() {
            System.out.println("我是一只鸟,可以飞");
        }
    }
    
    public interface IEat {
        void eat();
    }
    
    public interface IFly {
        void fly();
    }
    
    public interface IGatekeeper {
        void gatekeeper();
    }
    
  • 迪米特原则

    也叫最少知道原则。就是指一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合度。我们用上架的蛋糕种类来说明一下这个问题。

    假如经理需要知道这个分店上架了多少种类的蛋糕。这时候,店长就需要去统计,然后在告诉经理。

    public class Result {
        public static void main(String[] args) {
            Manager manager = new Manager();
            manager.checkNum(new StoreManager());
        }
    }
    
    public class Manager {
        public void checkNum(StoreManager storeManager) {
            // 经理一个一个的数,店长一个一个的记
            List<String> cakeList = new ArrayList<>();
            for (int i = 0; i < 20; i++) {
                cakeList.add(i + "种");
            }
            storeManager.checkCakeNum(cakeList);
        }
    }
    
    public class StoreManager {
        public int checkCakeNum(List<String> cakeList) {
            return cakeList.size();
        }
    }
    

    很显然,正常情况下,经理不会去一个一个的去数蛋糕的种类,这也不符合迪米特原则。经理只需要从店长哪里拿到一个结果就可以了。至于店长怎么知道的,自己数的还是员工说的,这就没有必要关心了。代码可以修改如下:

    public class Manager {
        public void checkNum(StoreManager storeManager) {
            int checkNum = storeManager.checkCakeNum();
        }
    }
    
    public class StoreManager {
        public int checkCakeNum() {
            List<String> cakeList = new ArrayList<>();
            for (int i = 0; i < 20; i++) {
                cakeList.add(i + "种");
            }
           return cakeList.size();
        }
    }
    
  • 里氏替换原则

    通俗点讲就是父类为其所用,那么子类必须为其所用,也一定够为其所用,而且不会改变程序的逻辑。准确点是一个软件实体如果适用于一个父类,那么一定适用于其子类,所有引用父类的地方必须能够透明的使用其子类的对象,子类对象能够替换父类对象,结果逻辑不变。也就是子类可以扩展父类的功能,但不能改变父类原有的功能。代码就不列举了。可以参考正方形、长方形和四边形的场景。正方形不能用长方形做父类。而是长方形和正方形都属于四边形。

  • 合成复用原则

    合成复用原则是指尽量使用对象组合/聚合而不是继承关系达到软件的复用目的。可以使系统更加灵活,降低类与类之间饿耦合度,一个类的变化对其他类造成的影响先对较少。例如,我们经常会封装一下工具类来进行使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容