软件设计原则

230 阅读7分钟

软件设计原则

在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员需要尽量根据6条原则来开发程序,从而提高软件的开发效率、节约软件开发成本和维护成本。

这6条原则分别为开闭原则、依赖倒置原则、里氏替代原则、接口隔离原则、迪米特法则、合成复用原则

开闭原则

**多扩展开放,对修改关闭。**在程序需要进行拓展的时候,在不去修改原代码的前提下变更它的行为。该原则提高了代码的可复用性和可维护性。

要想实现这种原则需要使用到抽象类或者接口。因为抽象的灵活性好、适应性广、相对稳定。当程序需要进行扩展的时候,只需要根据需要派生出一个新的实现类来扩展即可。

开闭原则实现代码

//定义一个Phone类,用于设置壁纸和查看壁纸
public class Phone {
    Wallpaper wallpaper;
    //设置壁纸
    public void setWallpaper(Wallpaper wallpaper){
        this.wallpaper=wallpaper;
    }
    //查看壁纸
    public void lookwallpaper(){
        System.out.println(wallpaper.getName());
    }
}
//定义一个抽象Walllpaper类
public abstract class Wallpaper {
    public abstract String getName();
}
//继承Wallpaper类,重写getName方法
public class AWallpaper extends Wallpaper{
    @Override
    public String getName() {
        return "A壁纸";
    }
}
//继承Wallpaper类,重写getName方法
public class BWallpaper extends Wallpaper{
    @Override
    public String getName() {
        return "B壁纸";
    }
}
//测试
public class Test {
    public static void main(String[] args) {
        Phone phone=new Phone();
        //Wallpaper wallpaper=new AWallpaper();
        Wallpaper wallpaper=new BWallpaper();
        phone.setWallpaper(wallpaper);
        phone.lookwallpaper();
    }
}
//要想重写设置手机的壁纸就只需要继承Wallpaper类

依赖倒置原则

设置代码结构的时候,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖抽象。也就是说应该对抽象进行编程而不是对实现进行编程,这样就降低了代码之间的耦合性。

首先想看如下代码

//定义一个Computer类
public class Computer {
    DellScreen dellScreen;
    IntelCpu intelCpu;

    public DellScreen getDellScreen() {
        return dellScreen;
    }

    public void setDellScreen(DellScreen dellScreen) {
        this.dellScreen = dellScreen;
    }

    public IntelCpu getIntelCpu() {
        return intelCpu;
    }

    public void setIntelCpu(IntelCpu intelCpu) {
        this.intelCpu = intelCpu;
    }
    public void allInfo(){
        dellScreen.info();
        intelCpu.info();
    }
}
//定义显示器
public class DellScreen {
    public void info(){
        System.out.println("使用的戴尔显示器");
    }
}
//定义Cpu
public class IntelCpu {
    public void info(){
        System.out.println("因特尔的CPU");
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Computer computer=new Computer();
        DellScreen dellScreen=new DellScreen();
        IntelCpu intelCpu=new IntelCpu();
        computer.setDellScreen(dellScreen);
        computer.setIntelCpu(intelCpu);
        computer.allInfo();
    }
}

上面的代码耦合性非常的高,因为电脑的配置可以随时更改并不固定,一旦需要修改配置,那么就需要对原代码进行修改。所以就应该将这些配置定义成一个接口,然后让具体的配置类去实现对应的接口,这样在变更配置的时候就不需要去修改原代码

代码如下

//重写Computer
public class Computer {
    Screen screen;
    Cpu cpu;

    public Screen getScreen() {
        return screen;
    }

    public void setScreen(Screen dellScreen) {
        this.screen = dellScreen;
    }

    public Cpu getCpu() {
        return cpu;
    }

    public void setCpu(Cpu Cpu) {
        this.cpu = Cpu;
    }
    public void allInfo(){
        screen.info();
        cpu.info();
    }
}
//定义Cpu接口
public interface Cpu {
    public void info();
}
//定义Screen接口
public interface Screen {
    public void info();
}
//实现Cpu接口
public class DellScreen implements Screen{
    @Override
    public void info(){
        System.out.println("使用的戴尔显示器");
    }
}
public class IntelCpu implements Cpu{
    @Override
    public void info(){
        System.out.println("因特尔的CPU");
    }
}
//实现Screen接口
public class DellScreen implements Screen{
    @Override
    public void info(){
        System.out.println("使用的戴尔显示器");
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Computer computer=new Computer();
        Screen screen=new DellScreen();
        //Cpu cpu=new IntelCpu();
        Cpu cpu=new AMDCpu();
        computer.setScreen(screen);
        computer.setCpu(cpu);
        computer.allInfo();
    }
}

里氏替代原则

任何基类可以出现的地方,子类一定可以出现。通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

在现实生活中有很多例子,比如正方形是长方形的一种特殊形式,气球鱼是鱼类但是不会游泳等。

首先先看如下代码

//定义Fish类
public class Fish {
    double speed;
    public void setSpeed(double speed){
        this.speed=speed;
    }
    public double getSwimmingTime(double km){
        return km/speed;
    }
}
//BalloonFish类继承Fish类
public class BalloonFish extends Fish{
    @Override
    public void setSpeed(double speed) {
        super.setSpeed(0);
    }
}
//TilapiaFish类继承Fish类
public class TilapiaFish extends Fish{
}
//测试类
public class Test {
    public static void main(String[] args) {
        Fish fish1=new BalloonFish();
        Fish fish2=new TilapiaFish();
        fish1.setSpeed(300);
        fish2.setSpeed(300);
        System.out.println(fish1.getSwimmingTime(200));
        System.out.println(fish2.getSwimmingTime(200));
    }
}
//结果
Infinity
0.6666666666666666
    

以上代码违反了里氏替代原则,因为子类不能重写父类非抽象的方法,但是由于气球鱼不能游泳所以只能够去重写父类非抽象的方法设置速度为0。以上代码可以创建一个更高层次的类比如动物类,气球鱼虽然不能游泳,但是有行走的能力,所以可以求出行走的速度,这样就可以避免对父类非抽象代码的重写。

接口隔离原则

客户端不应该依赖它不需要的接口;一个类另一个类的依赖应该建立在最小的接口上,不应当使用总接口。

比如说有的机器人能跑能跳能唱歌跳舞,但是有的机器人就只会跑和跳舞。显然这就违背了接口隔离的原则。这是我们应当将这些功能抽离到单独的接口。

代码如下

//跳舞接口
public interface Dance {
    public void dance();
}
//跳高接口
public interface Jump {
    public void jump();
}
//跑步接口
public interface Run {
    public void run();
}
//唱歌接口
public interface Sing {
    public void sing();
}
//A机器人具有跳舞和跑步功能
public class ARobot implements Dance,Run{
    @Override
    public void dance() {
        System.out.println("跳舞");
    }

    @Override
    public void run() {
        System.out.println("跑步");
    }
}
//B机器人具有跳舞、跳高、跑步、唱歌功能
public class BRobot implements Dance,Jump,Run,Sing{
    @Override
    public void dance() {
        System.out.println("跳舞");
    }

    @Override
    public void jump() {
        System.out.println("跳高");
    }

    @Override
    public void run() {
        System.out.println("跑步");
    }

    @Override
    public void sing() {
        System.out.println("唱歌");
    }
}

迪米特法则

又称最少知道原则,一个对象对其他对象保持最少的了解,对象与对象之间通过第三方对象来进行通信,降低类之间的耦合度,提高代码的独立性。

在现实生活中就有这样的例子,比如你去求职,你不会跟Boss直接谈话,你会和HR进行一些交流,HR又会和Boss进行交流,这样你和Boos没有直接进行谈话也能够得到他得批准入职。

如如下代码

public class Boss {
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Staff {
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class HR {
    public Boss boss;
    public Staff staff;

    public Boss getBoss() {
        return boss;
    }

    public void setBoss(Boss boss) {
        this.boss = boss;
    }

    public Staff getStaff() {
        return staff;
    }

    public void setStaff(Staff staff) {
        this.staff = staff;
    }
    //HR和Boss进行交谈
    public void HB(){
        System.out.println("HR:"+staff.getName()+"说一万一个月");
    }
    //HR和Staff进行交谈
    public void SB(){
        System.out.println("HR:"+boss.getName()+"说你可以回家了");
    }
}

合成复用原则

是指尽量使用对象组合/聚合的方式来实现,其次才考虑使用继承的方式来实现

通常类的复用分为继承复用和合成复用

继承复用有易实现和简单的优点,但他也有一下的缺点

1、继承的复用破坏了类的封装性,因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称之为“白箱”复用

2、子类与父类的耦合度较高,父类实现的任何改变都会导致子类的实现发生变化,这不利于类的拓展和维护

3、限制了复用的灵活性,从父类继承而来的实现是静态的,在编译的时候就已经定义了,所以在运行的时候不会发生变化

采用组合复用或者聚合复用的时候,可以将已有对象纳入到新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能。它有着以下的优点

1、维持了类的封装性,因为成员对象的内部实现对新对象来说是不可见的,所以这种复用又称“黑箱”复用

2、对象间的耦合度降低,可以在类的成员位置声明抽象

3、复用的灵活度高,这种复用可以在运行时动态进行,新对象可以动态地引用与对象类型相同的对象

上面的类结构就是使用了继承复用,一旦要增加新的颜色的汽车又得重写继承,使得类结构越来越复杂。我们可以使用合成复用原则进行重构

显然类结构一下就减轻了很多

总结

学习软件设计原则是学习设计模式的基础,在真正设计软件的时候不一定要满足这些原则,应当根据实际的要求去实现,要在适合的场景下使用这些原则。