笔记 :封装、抽象、继承、多态分别可以解决哪些编程问题?

08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?-极客时间

封装(Encapsulation)

如果我们对类中属性的访问不做限制,那任何代码都可以访问、修改类中的属性,虽然这样看起来更加灵活,但从另一方面来说,过度灵活也意味着不可控,属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。

WHAT

信息隐藏、数据访问保护

HOW

访问权限控制(编程语言本身提供一定的语言机制来支持)

WHY

提高代码可维护性;降低接口复杂度,提高类的易用性。

抽象(Abstraction)

实际上,抽象这个特性是非常容易实现的,并不需要非得依靠接口类或者抽象类这些特殊语法机制来支持。换句话说,并不是说一定要为实现类(PictureStorageImpl)抽象出接口类(PictureStorage),才叫作抽象。即便不编写 IPictureStorage 接口类,单纯的 PictureStorage 类本身就满足抽象特性。

之所以这么说,那是因为,类的方法是通过编程语言中的 “函数” 这一语法机制来实现的。通过函数包裹具体的实现逻辑,这本身就是一种抽象。调用者在使用函数的时候,并不需要去研究函数内部的实现逻辑,只需要通过函数的命名、注释或者文档,了解其提供了什么功能,就可以直接使用了。比如,我们在使用 C 语言的 malloc () 函数的时候,并不需要了解它的底层代码是怎么实现的。

WHAT

让方法调用者只在意功能,不用在意实现逻辑。

HOW

通过接口类或者抽象类实现,特殊语法机制非必须。

Why

提高代码的扩展性、维护性;降低复杂度,减少细节负担。

继承(Inheritance)

我们再上升一个思维层面,去思考继承这一特性,可以这么理解:我们代码中有一个猫类,有一个哺乳动物类。属于哺乳动物,从人类认知的角度上来说,是一种 is-a 关系。我们通过继承来关联两个类,反应真实世界中的这种关系,非常符合人类的认知,而且,从设计的角度来说,也有一种结构美感。

WHAT

表示 is-a 关系,分为单继承和多继承。

HOW

需要编程语言提供的特殊语法机制。例如 Java 的 “extends”,C++ 的 “:” 。

WHY

解决代码复用问题。

多态(Polymorphism)

WHAT

子类替换父类,在运行时调用子类的实现。

HOW

  • 继承加方法重写

    public class DynamicArray {
      private static final int DEFAULT_CAPACITY = 10;
      protected int size = 0;
      protected int capacity = DEFAULT_CAPACITY;
      protected Integer[] elements = new Integer[DEFAULT_CAPACITY];
      
      public int size() { return this.size; }
      public Integer get(int index) { return elements[index];}
      //...省略n多方法...
      
      public void add(Integer e) {
        ensureCapacity();
        elements[size++] = e;
      }
      
      protected void ensureCapacity() {
        //...如果数组满了就扩容...代码省略...
      }
    }
    
    public class SortedDynamicArray extends DynamicArray {
      @Override
      public void add(Integer e) {
        ensureCapacity();
        int i;
        for (i = size-1; i>=0; --i) { //保证数组中的数据有序
          if (elements[i] > e) {
            elements[i+1] = elements[i];
          } else {
            break;
          }
        }
        elements[i+1] = e;
        ++size;
      }
    }
    
    public class Example {
      public static void test(DynamicArray dynamicArray) {
        dynamicArray.add(5);
        dynamicArray.add(1);
        dynamicArray.add(3);
        for (int i = 0; i < dynamicArray.size(); ++i) {
          System.out.println(dynamicArray.get(i));
        }
      }
      
      public static void main(String args[]) {
    		//父类对象可以引用子类对象,
    		//也就是可以将 SortedDynamicArray 传递给 DynamicArray
        DynamicArray dynamicArray = new SortedDynamicArray();
        test(dynamicArray); // 打印结果:1、3、5
      }
    }
    

    多态这种特性也需要编程语言提供特殊的语法机制来实现。在上面的例子中,我们用到了三个语法机制来实现多态。

    • 第一个语法机制是编程语言要支持父类对象可以引用子类对象,也就是可以将 SortedDynamicArray 传递给 DynamicArray。
    • 第二个语法机制是编程语言要支持继承,也就是 SortedDynamicArray 继承了 DynamicArray,才能将 SortedDyamicArray 传递给 DynamicArray。
    • 第三个语法机制是编程语言要支持子类可以重写(override)父类中的方法,也就是 SortedDyamicArray 重写了 DynamicArray 中的 add () 方法。
  • 接口类

    public interface Iterator {
      boolean hasNext();
      String next();
      String remove();
    }
    
    public class Array implements Iterator {
      private String[] data;
      
      public boolean hasNext() { ... }
      public String next() { ... }
      public String remove() { ... }
      //...省略其他方法...
    }
    
    public class LinkedList implements Iterator {
      private LinkedListNode head;
      
      public boolean hasNext() { ... }
      public String next() { ... }
      public String remove() { ... }
      //...省略其他方法... 
    }
    
    public class Demo {
      private static void print(Iterator iterator) {
        while (iterator.hasNext()) {
          System.out.println(iterator.next());
        }
      }
      
      public static void main(String[] args) {
        Iterator arrayIterator = new Array();
        print(arrayIterator);
        
        Iterator linkedListIterator = new LinkedList();
        print(linkedListIterator);
      }
    }
    

    在这段代码中,Iterator 是一个接口类,定义了一个可以遍历集合数据的迭代器。Array 和 LinkedList 都实现了接口类 Iterator。我们通过传递不同类型的实现类(Array、LinkedList)到 print (Iterator iterator) 函数中,支持动态的调用不同的 next ()、hasNext () 实现。

    当我们往 print (Iterator iterator) 函数传递 Array 类型的对象的时候,print (Iterator iterator) 函数就会调用 Array 的 next ()、hasNext () 的实现逻辑;

    当我们往 print (Iterator iterator) 函数传递 LinkedList 类型的对象的时候,print (Iterator iterator) 函数就会调用 LinkedList 的 next ()、hasNext () 的实现逻辑。

  • duck-typing

    鸭子类型(英语:duck typing)在程序设计中是动态类型的一种风格。

    在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由 "当前方法和属性的集合" 决定。

    class Logger:
        def record(self):
            print(“I write a log into file.”)
            
    class DB:
        def record(self):
            print(“I insert data into db. ”)
            
    def test(recorder):
        recorder.record()
    
    def demo():
        logger = Logger()
        db = DB()
        test(logger)
        test(db)
    

    Logger 和 DB 两个类没有任何关系,既不是继承关系,也不是接口和实现的关系,但是只要它们都有定义了 record () 方法,就可以被传递到 test () 方法中,在实际运行的时候,执行对应的 record () 方法。

    也就是说,只要两个类具有相同的方法,就可以实现多态,并不要求两个类之间有任何关系,这就是所谓的 duck-typing,是一些动态语言所特有的语法机制。

    而像 Java 这样的静态语言,通过继承实现多态特性,必须要求两个类之间有继承关系,通过接口实现多态特性,类必须实现对应的接口。

WHY

提高了代码的可扩展性、复用性。

课堂讨论

  • 你熟悉的编程语言是否支持多重继承?如果不支持,请说一下为什么不支持。如果支持,请说一下它是如何避免多重继承的副作用的。

Java 不支持多重继承的原因

多重继承有副作用:钻石问题 (菱形继承)。

假设类 B 和类 C 继承自类 A,且都重写了类 A 中的同一个方法,而类 D 同时继承了类 B 和类 C,那么此时类 D 会继承 B、C 的方法,那对于 B、C 重写的 A 中的方法,类 D 会继承哪一个呢?这里就会产生歧义。

考虑到这种二义性问题,Java 不支持多重继承。但是 Java 支持多接口实现,因为接口中的方法,是抽象的(从 JDK1.8 之后,接口中允许给出一些默认方法的实现,这里不考虑这个),就算一个类实现了多个接口,且这些接口中存在某个同名方法,但是我们在实现接口的时候,这个同名方法需要由我们这个实现类自己来实现,所以并不会出现二义性的问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LMAO6688

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

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

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

打赏作者

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

抵扣说明:

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

余额充值