设计模式前言-设计原则、面向对象和Java语言特性

设计原则是心法,设计模式是招式。掌握心法,以不变应万变,无招胜有招。所以在学习设计模式之前,个人建议先了解本文提到的内容。

一、设计原则

提到设计原则,你能想起哪些设计原则?在编程的时候,你是否会思考这段代码有没有违背哪些原则?接下来,我们一起温习下六大设计原则:单一原则、开闭原则、里氏替换原则、迪米特法则(最少知道原则)、接口隔离原则、依赖倒置原则。

六大设计原则首字母合起来,称为SOLID(稳定的意思),其中里氏替换原则和迪米特法则共用一个L。

1.单一原则(SRP)

单一原则(Single Responsibility Principle)。它就是长辈们口中经常提到的“一个函数应该只做一件事”。

定义

A class or module should have a single reponsibility. 一个类或模块应该只完成一个职责(或功能)。

建议

切记不要过于一味的追求单一原则,还要考虑高内聚。

2.开闭原则(OCP)

开闭原则(Open Closed Principle)。工作中,是否经历过增加一个需求,结果导致原先正常的功能bug了。

定义

Software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification. 软件实体(模块,类,函数等等)应该对扩展开放,但是对修改关闭。

建议

开闭原则并不是完全拒绝修改,而是以最小的修改代码的代价来完成新功能的开发。

3.里氏替换原则(LSP)

里氏替换原则(Liskov Substitution Principle)。工作中,在重写过父类函数时,是否在意重写的函数与父类的函数逻辑是否一致?

定义

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。

案例


public class Parent {

    private static final String DEFAULT_NAME = "defaultName";
    private static final String DEFAULT_CODE = "defaultCode";

    public void create(String name, String code){

        if(name == null && code == null){
            name = DEFAULT_NAME;
            code = DEFAULT_CODE;
        }

        // ......
    }
}

// 以下子类不符合里氏替换原则,因为逻辑被替换了。父类当参数为空给的是默认值,而子类则是直接抛出异常

public class SubClass extends Parent{

    @Override
    public void create(String name, String code) {
        if(name == null || code == null){
            throw new IllegalArgumentException("name或code不能为null");
        }
        super.create(name, code);
    }
}

4.迪米特法则(LOD)

迪米特法则(Law of Demeter)。也叫做最小知识原则(The Least Knowledge Principle)。

写出“高内聚,低耦合”的代码。

定义

Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers. 每个模块只应该了解那些与它关系密切的模块的有限知识。或者说,每个模块只和自己的朋友“说话”,不和陌生人“说话”。

迪米特法则是希望减少类之间的耦合,让类越独立越好。也就是高内聚低耦合。

5.接口隔离原则(ISP)

接口隔离原则(Interface Segregation Principle)。

定义

Clients should not be forced to depend upon interfaces that they do not use. 客户端不应该被强迫依赖它不需要的接口。

案例

例如你需要提供一个用户查询的相关接口,不提供增删改的接口。你会如何处理?

// 查询接口
public interface UserService {
    
    String queryById(String id);
}

// 增删改接口
public interface UserManagerService extends UserService {

    void insert(String name);

    void delete(String id);

    void update(String id , String name);
    
}

@Component
public class UserServiceImpl implements UserManagerService {

    @Override
    public void insert(String name) {

    }

    @Override
    public void delete(String id) {

    }

    @Override
    public void update(String id, String name) {

    }

    @Override
    public String queryById(String id) {
        return null;
    }
}

// 可以选择注入

@Autowired
private UserService userService;
@Autowired
private UserManagerService userManagerService;

6.依赖反转原则(DIP)

依赖反转原则(Dependency Inversion Principle),也称为依赖倒置原则。

定义

High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.

高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

案例:Servlet

Tomcat的Engine添加Servlet属性,Spring的DispatcherServlet实现了Servlet接口。两者都依赖于实现,而是依赖于接口。

二、面向对象

基本特征

封装(Encapsulation)

封装是为了提高代码可维护性和易用性,降低接口复杂度,来隐藏信息或者保护数据。

Java语言利用访问修饰符来达到封装的目的。private,protected,public三个级别,当不使用访问修饰符时,类默认是同一个包下可见,接口默认是public级别。

继承(Inheritance)

继承是为了代码复用。将公共部分提取到父类中,让不同的子类来继承父类。

Java语言支持单继承多实现。

为什么不支持多继承?设计者的初衷,让java更简单,所以选择了单继承多实现,避免多继承中函数冲突的问题。

1.8之后允许接口有default的方法,如果多接口中默认实现的函数定义一致,则会失去默认实现的意义,必须实现类自己来实现。


public interface InterA {

    default  void action(){
        System.out.println("InterA action");
    }
}

public interface InterB {

    default  void action(){
        System.out.println("InterB action");
    }
}

// 提示:Inter inherits unrelated defaults for action() from types InterA and InterB
public class Inter implements  InterA, InterB {
}

// 必须要实现action函数
public class Inter implements  InterA, InterB {
    @Override
    public void action() {
        new InterA(){}.action();
    }
}

多态(Polymorphism)

多态可以提高代码的扩展性和复用性,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。

多态特性的实现方式,JAVA语言有“方法重写”和“接口”,还有duck-typing语法。

三、Java语言特性

该章节为java语言的特性,在接下来的设计模式中部分代码示例会使用到。

类的加载顺序

public class Parent {

    static{
        System.out.println("Parent static {} ");
    }

    public Parent(){
        System.out.println("Parent constructor ");
    }
}

public class Child extends Parent {

    static {
        System.out.println("Child static {} " + ChildSubClassA.INT);
    }

    {
        System.out.println("Child {} " + ChildSubClassA.INT);
    }

    public Child() {
        System.out.println("Child constructor ");
    }

    public static void main(String[] args) {
        new Child();
    }

    private static class ChildSubClassA {
        public static Child INT;

        static {
            System.out.println("ChildSubClassA static {} " + INT);
            INT = new Child();
        }
    }
}

// 打印如下

// Parent static {}
// ChildSubClassA static {} null
// Parent constructor
// Child {} null
// Child constructor
// Parent static {} xxxxx
// Parent constructor
// Child {} xxxxx
// Child constructor

JAVA特性:静态多分派,动态单分派

当我们实例化一个对象时,格式为“声明类型 变量名 = new 实际类型();”,申明类型也称为变量的静态类型。

  • 所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。
  • 在运行期根据实际类型确定方法执行版本的分派过程称为动态分派
public class Parent {

    public void show(Parent parent){
        System.out.println("Parent.show(Parent)");
    }

    public void show(Children children){
        System.out.println("Parent.show(Children)");
    }
}

public class Children extends Parent {

    public void show(Parent parent){
        System.out.println("Children.show(Parent)");
    }

    public void show(Children children){
        System.out.println("Children.show(Children)");
    }
}


public class Main {

    public static void main(String[] args) {
        Parent parent = new Parent();
        Parent parentChildren = new Children();
        Children children = new Children();
        
        // 思考下,编译器会定义几个方法?
        parent.show(parentChildren);
        parentChildren.show(parent);
        parentChildren.show(children);
        children.show(parent);
    }
}

根据静态多分派,以上代码编译器会定义三个方法,分别为:Parent.show(Parent)、Parent.show(Children)、Children.show(Parent)。以下为编译后文件内容

......

26 invokevirtual #6 <cn/hgy/singledispatch/Parent.show>
29 aload_2
30 aload_1
31 invokevirtual #6 <cn/hgy/singledispatch/Parent.show>
34 aload_2
35 aload_3
36 invokevirtual #7 <cn/hgy/singledispatch/Parent.show>
39 aload_3
40 aload_1
41 invokevirtual #8 <cn/hgy/singledispatch/Children.show>

.....

根据动态单分派,parentChildren.show(parent);和children.show(parent);最终会执行到同一个方法。打印效果如下:

Parent.show(Parent)
Children.show(Parent)
Children.show(Children)
Children.show(Parent)

关注我

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑾析编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值