编程要搞明白的东西(二)

一、简介

一直在学习设计模式和面向对象,对于这个过程中发现,一直少不了封装继承多态这些东西,于是就总结一下。

二、面向对象编程基础

2.1 面向对象编程概述

面向对象编程(Object-Oriented Programming, OOP)是一种常用的编程范式,它将程序设计问题分解成对象,通过对象之间的交互来解决问题。在面向对象编程中,将现实世界中的实体抽象为对象,每个对象都有自己的状态和行为。

面向对象编程的基本概念包括封装、继承和多态。其中,封装指的是将数据和方法封装在对象中,隐藏实现细节,并提供公共接口与外部交互。继承允许子类继承父类的属性和方法,并可以在此基础上进行扩展和重写。多态允许同一个方法在不同对象上产生不同的行为,提高代码的灵活性和扩展性。

面向对象编程的原则包括单一职责原则(Single Responsibility Principle, SRP)、开放封闭原则(Open-Closed Principle, OCP)、依赖倒转原则(Dependency Inversion Principle, DIP)等。这些原则旨在提高代码的可维护性、可扩展性和可复用性,使程序更加稳定和易于理解。

面向对象编程广泛应用于软件开发领域,能够更好地组织和管理复杂的代码逻辑。它提供了一种抽象和模块化的编程方式,使得代码更易于理解和维护。同时,面向对象编程也提供了一些设计模式和技巧,可以帮助开发人员更好地解决实际问题。

2.2 类和对象

2.2.1 类的定义和特点

类是面向对象编程中的基本概念之一,它可以看作是一种模板或蓝图,用于创建具有相似属性和行为的对象。

类的定义包括以下几个要素:

  • 类名:用于标识类的名称,通常使用大写字母开头的驼峰命名法。
  • 属性:类的属性是描述对象状态的变量,也称为成员变量。属性可以是任何数据类型,例如整数、字符串、列表等。
  • 方法:类的方法是定义在类中的函数,用于描述对象的行为和操作。方法可以访问和修改对象的属性。

类的特点如下:

  • 封装:类可以对数据和方法进行封装,隐藏实现细节,只暴露必要的接口给外部使用。
  • 继承:通过继承机制,子类可以继承父类的属性和方法。子类可以在继承的基础上进行扩展和重写,实现代码的重用性和扩展性。
  • 多态:多态允许不同的对象对相同的消息作出不同的响应。同一个方法可以在不同的对象上产生不同的行为,提高代码的灵活性和可扩展性。

2.2.2 对象的创建和使用

要创建一个对象,首先需要使用类作为模板,通过类来实例化一个具体的对象。对象是类的具体实例,它包含了类中定义的属性和方法。
对象的创建可以通过以下步骤完成:

  1. 使用关键字new和类名来创建一个对象,例如 obj = new MyClass()
  2. 在创建对象时,可以传递参数给类的构造函数,用于初始化对象的属性。
  3. 创建对象后,可以使用点号.来访问对象的属性和方法,例如 obj.method()obj.property
    以下是一个简单的示例代码,演示了类的定义、对象的创建和使用:
public class Person {
    
    
    private String name;
    private int age;

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
    
    
        System.out.println("Hello, my name is " + this.name);
    }

    public static void main(String[] args) {
    
    
        Person person = new Person("Alice", 25);

        // 访问对象的属性
        System.out.println("Name: " + person.name);
        System.out.println("Age: " + person.age);

        // 调用对象的方法
        person.sayHello();
    }
}

输出结果:

Name: Alice
Age: 25
Hello, my name is Alice

以上代码中,首先定义了一个名为Person的类,它具有name和age两个属性,以及一个say_hello方法。然后,通过创建Person类的一个对象person,并传入初始化参数进行属性赋值。最后,使用对象的属性和方法进行访问和调用。
这只是一个简单的示例,类和对象的使用可以更加复杂和灵活,以适应不同的编程需求。

2.3 封装、继承与多态的关系

2.3.1 封装的概念和优势

封装是面向对象编程中的一个重要概念,它将数据和功能封装在类中,通过对外暴露有限的接口,隐藏了内部的实现细节。封装可以实现数据的保护和安全,提高代码的可维护性和复用性。

封装的优势包括:

  1. 数据隐藏:封装可以将类的属性隐藏起来,只提供必要的接口方法来访问和修改属性。这样可以避免外部直接访问和修改对象的属性,提高数据的安全性和可靠性。

  2. 隔离复杂性:封装将对象的实现细节隐藏起来,对外只暴露功能接口。这样可以将复杂的实现细节封装在内部,使得外部使用者无需了解和关心内部的复杂逻辑,降低了系统的耦合度。

  3. 代码复用:封装可以将一组相关的数据和功能封装在一个类中,使得可以轻松创建多个实例对象。这样可以实现代码的复用,避免重复编写相似的代码,提高了开发效率。

  4. 接口统一性:封装通过提供统一的接口方法,可以隐藏不同对象的差异性,使得外部使用者无需关心具体的对象类型,只需要了解统一的接口调用方式。这样可以实现接口的统一性,简化了系统的复杂性。

数据封装的方式可以通过定义私有属性和公有方法来实现。私有属性和方法通常以双下划线__开头,在类的内部使用;而公有属性和方法可以直接在外部使用。通过合理地定义私有和公有成员,可以控制对属性的访问和修改,实现对数据的保护和安全。

2.3.2 继承的概念和作用

继承是面向对象编程中的一个重要概念,它描述了类与类之间的一种关系,通过继承机制,子类可以继承父类的属性和方法。继承可以实现代码的重用性和扩展性,减少了代码的冗余,提高了代码的可维护性和灵活性。

继承的作用包括:

  1. 代码复用:继承可以使得子类继承父类的属性和方法,从而实现代码的复用。子类不需要重新编写已经存在的代码,而是可以直接使用父类的实现,提高了代码的复用性。

  2. 扩展功能:子类可以在继承的基础上进行扩展和修改,添加新的属性和方法,重写已有的方法。这样可以实现功能的扩展和定制,满足不同对象的特定需求。

  3. 统一接口:继承可以使得不同的类具有相同的接口。子类可以作为父类的替代品使用,而对外部使用者来说,无需关心具体的对象类型,只需要使用统一的接口方法,提高了代码的灵活性和可扩展性。

继承可以分为单继承和多继承两种形式:

  • 单继承:一个子类只能继承自一个父类。子类可以从父类中继承属性和方法,并在此基础上进行扩展和修改。Python是支持单继承的。

  • 多继承:一个子类可以同时继承自多个父类。子类从多个父类中继承属性和方法,并在此基础上进行扩展和修改。多继承可以通过逗号分隔的方式指定多个父类,例如class SubClass(BaseClass1, BaseClass2)。多继承在某些情况下可以提供更灵活的代码组织方式,但也容易引起命名冲突和继承关系的复杂性。

2.3.3 多态的概念和实现方式

多态是面向对象编程的一个重要特性,它描述了不同对象类型对相同消息作出不同响应的能力。多态可以提高代码的灵活性和扩展性,实现接口的统一性。

多态的概念可以通过以下方式理解:同一个方法在不同的对象上产生不同的行为。它允许不同的对象根据自身的特点,对相同的消息做出不同的处理。多态基于继承关系,子类可以对父类的方法进行重写,从而实现多态效果。

多态的实现方式包括:

  1. 向上转型:将子类对象赋值给父类类型的变量。通过向上转型,可以实现父类类型对子类对象的引用,从而实现多态的效果。向上转型可以使得子类对象以父类的形式存在,调用父类的方法。这样可以隐藏子类的具体实现,只关注父类的接口。

  2. 向下转型:如果将父类对象强制转换为子类类型,则称为向下转型。向下转型可以在需要时将父类对象恢复为子类对象,以便访问子类的特定属性和方法。但是,向下转型需要注意安全性,需要确保被转换的父类对象实际上是子类对象。

  3. 动态绑定:在运行时根据对象的实际类型来调用对应的方法。在多态中,调用方法时编译器不会确定实际调用的是哪个类的方法,而是在运行时动态绑定到正确的方法。这样使得系统可以根据实际的对象类型来决定方法的调用。

  4. 静态绑定:在编译时根据变量声明的类型来确定调用的方法。静态绑定发生在没有多态的情况下,编译器根据变量的声明类型决定调用哪个方法。

多态在面向对象编程中起到了非常重要的作用,可以提高代码的灵活性和可扩展性,使得对象在运行时能够根据具体的类型表现出不同的行为。

三、封装

3.1 封装的定义和原则

封装是面向对象编程中的一个重要概念,它指的是将数据和相关的操作方法封装在类中,通过访问控制符进行权限管理,从而实现对数据和功能的保护和控制。

封装的原则包括:

  1. 数据隐藏原则(Information Hiding):将类的内部实现细节隐藏起来,只暴露必要的接口给外部使用者。这样可以避免外部直接访问和修改类的内部数据,提高数据的安全性和可靠性。

  2. 单一职责原则(Single Responsibility Principle):一个类应该只负责一项职责,封装相关的数据和方法。这样可以使得类的结构更加清晰,提高代码的可维护性和复用性。

  3. 最小暴露原则(Principle of Least Privilege):仅将需要对外公开的内容暴露出来,其他内容尽量隐藏起来。这样可以减少不必要的访问权限,降低系统被滥用的风险。

  4. 迪米特法则(Law of Demeter):一个对象应该尽量减少与其他对象之间的交互,尽量只与直接的朋友对象交互。这样可以降低对象之间的耦合度,提高代码的灵活性和可维护性。

3.2 封装的实现方法

3.2.1 成员变量的封装

成员变量的封装可以通过定义私有属性和公共方法来实现。在类的内部使用;而公共方法可以在外部使用。下面是一个示例:

public class Person {
    
    
    private String name;
    private int age;

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int newAge) {
    
    
        this.age = newAge;
    }

    public static void main(String[] args) {
    
    
        Person person = new Person("Alice", 25);
        System.out.println(person.getName());  // 输出:Alice
        person.setName("Bob");
        System.out.println(person.getName());  // 输出:Bob
    }
}

在上述示例中,nameage是私有属性,外部无法直接访问和修改。通过getName()setName()方法来获取和修改name属性的值。

3.2.2 方法的封装

方法的封装可以通过将一些私有方法定义为仅在类内部使用的形式,通过公共方法来间接调用私有方法。下面是一个示例:

public class Calculator {
    
    
    private int add(int a, int b) {
    
    
        return a + b;
    }
    
    public int performAddition(int a, int b) {
    
    
        return add(a, b);
    }

    public static void main(String[] args) {
    
    
        Calculator calculator = new Calculator();
        int result = calculator.performAddition(3, 5);
        System.out.println(result);  // 输出:8
    }
}

在上述示例中,私有方法add()仅在类内部使用。公共方法performAddition()通过调用私有方法add()来实现对方法的封装。

3.3 封装的优势和应用场景

封装具有以下优势:

  1. 数据保护和安全性:封装可以将数据隐藏起来,只提供公共方法进行访问和修改。这样可以避免外部直接访问和修改数据,提高数据的安全性和可靠性。

  2. 代码可维护性:封装可以将类的实现细节隐藏起来,对外部使用者只暴露必要的接口。这样可以降低类之间的耦合度,使得代码更易于理解和维护。

  3. 代码复用:封装将数据和相关的方法封装在类中,可以轻松创建多个实例对象。这样可以实现代码的复用,提高开发效率。

  4. 接口统一性:封装通过提供统一的接口方法,可以隐藏不同对象的差异性。外部使用者无需关心具体的对象类型,只需要使用统一的接口调用方式。

封装的应用场景包括但不限于:

  • 在构建大型软件系统时,可以通过封装来组织和管理代码,提高代码的可维护性和复用性。

  • 在保护敏感数据时,可以通过封装将数据隐藏起来,只提供受控的访问方式,提高数据的安全性。

  • 在开发框架和库时,通过封装对外暴露接口,使得使用者只需要关注接口的调用方式,而不需要了解具体的实现细节。

封装是面向对象编程中的重要概念,在实际的软件开发中具有广泛的应用价值。

四、继承

4.1 继承的定义和分类

继承是一种面向对象编程的概念,指一个新的类可以从现有类中派生出来。新的类会自动获得现有类的方法和属性,从而可以在不破坏现有类代码的情况下添加新的功能或修改现有功能。继承可以分为单继承和多继承两种类型。

  • 单继承:子类只继承一个父类的属性和方法。
  • 多继承:子类可以同时继承多个父类的属性和方法。

4.2 继承的语法和特点

4.2.1 extends 关键字的使用

Java 中使用 extends 关键字来实现继承关系,具体语法如下:

class SubClass extends SuperClass {
    
    
    // 子类的代码
}

其中,SubClass 是子类的名称,SuperClass 是父类的名称。继承意味着子类自动拥有了父类的所有非私有成员变量和方法,包括构造方法、普通方法、静态方法等。此外,在子类中可以新增属性和方法,或者覆盖父类的某些方法。

4.2.2 方法的重写和访问控制

在 Java 中,子类可以重写(override)父类的方法。方法的重写要求子类方法的返回类型、方法名和参数列表与父类方法相同。访问权限不能更低,但可以更高。也就是说,子类可以将父类中的方法改为 public 或 protected 级别,但不能改为 private 级别。具体语法如下:

class SuperClass {
    
    
    public void method() {
    
    
        System.out.println("Hello from SuperClass!");
    }
}

class SubClass extends SuperClass {
    
    
    @Override
    public void method() {
    
     // 重写 SuperClass 的方法
        System.out.println("Hello from SubClass!");
    }
}

在上述示例中,SubClass 继承自 SuperClass,并重写了父类的 method() 方法。在子类中使用 @Override 注解来表示该方法是一个重写的方法。

需要注意的是,当子类中的方法和父类中的方法相同时,子类中的方法会覆盖掉父类中的方法。也就是说,当调用子类对象的该方法时,实际上是调用了子类中重写的方法,而不是父类中原有的方法。

4.3 继承的优势和应用场景

继承的主要优势包括:

  • 代码重用性:继承使得子类可以重用父类的代码,提高了代码复用率。
  • 扩展性:继承使得子类可以在不破坏原有代码的情况下增加新的功能或修改现有功能。

继承适用于那些具有相似特征和行为的类之间。例如,在一个图形应用程序中,圆形、正方形、三角形等都是图形的一种,它们共享一些基本属性(如面积)和方法(如画图),因此可以从一个基类 Shape 派生出所有这些形状的类。

五、多态

5.1 多态的概念和原理

多态是面向对象编程中的一个重要概念,它指的是同一类型的对象,在不同的情况下表现出不同的行为。多态的实现依赖于向上转型、向下转型和动态绑定。

  • 向上转型(Upcasting):将子类对象赋值给父类引用变量,即将一个子类类型的对象转换为父类类型。这种转型是安全的,因为子类对象继承了父类的属性和方法,所以可以通过父类引用访问子类的成员(方法或属性)。示例代码如下:

    SuperClass obj = new SubClass(); // 向上转型
    
  • 向下转型(Downcasting):将父类对象转换为子类对象,即将一个父类类型的引用变量转换为子类类型。这种转型需要注意类型转换的可行性,只有在运行时确保转型是安全的情况下才能进行。示例代码如下:

    SubClass obj = (SubClass) superObj; // 向下转型
    
  • 动态绑定:在程序运行时根据实际对象的类型来决定调用哪个类的方法。在多态中,当子类对象被赋给父类引用后,通过父类引用调用某个方法时,实际上会执行子类中重写的方法。这种绑定方式称为动态绑定或运行时绑定。

  • 静态绑定:在编译期间根据引用变量的类型来决定调用哪个类的方法。静态绑定发生在没有多态的情况下,即在调用非重写的普通方法、私有方法、静态方法以及 final 方法时。

5.2 多态的实现方式

5.2.1 抽象类和接口的应用

抽象类和接口是实现多态的两种常见方式。

抽象类是一个不能被实例化的类,它可以包含抽象方法和普通方法。抽象方法没有具体的实现,需要在子类中进行重写。通过继承抽象类,子类可以实现抽象方法从而表现出不同的行为。抽象类可以作为多态的父类使用。示例代码如下:

abstract class Shape {
    
    
    public abstract void draw();
}

class Circle extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("Drawing a circle");
    }
}

class Rectangle extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("Drawing a rectangle");
    }
}

public class Main {
    
    
    public static void main(String[] args) {
    
    
        Shape shape1 = new Circle();
        Shape shape2 = new Rectangle();
        
        shape1.draw(); // 调用子类的方法
        shape2.draw(); // 调用子类的方法
    }
}

接口是一种完全抽象的类型,它定义了一组方法的签名,但没有具体的实现。一个类可以实现多个接口,通过实现接口中的方法来表达不同的行为。接口也可以作为多态的类型使用。示例代码如下:

interface Drawable {
    
    
    void draw();
}

class Circle implements Drawable {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("Drawing a circle");
    }
}

class Rectangle implements Drawable {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("Drawing a rectangle");
    }
}

public class Main {
    
    
    public static void main(String[] args) {
    
    
        Drawable drawable1 = new Circle();
        Drawable drawable2 = new Rectangle();
        
        drawable1.draw(); // 调用实现类的方法
        drawable2.draw(); // 调用实现类的方法
    }
}

5.2.2 实现多态的代码示例

下面是一个使用多态的代码示例,展示了向上转型和动态绑定的效果:

class Animal {
    
    
    public void makeSound() {
    
    
        System.out.println("Animal makes sound");
    }
}

class Cat extends Animal {
    
    
    @Override
    public void makeSound() {
    
    
        System.out.println("Cat meows");
    }
}

class Dog extends Animal {
    
    
    @Override
    public void makeSound() {
    
    
        System.out.println("Dog barks");
    }
}

public class Main {
    
    
    public static void main(String[] args) {
    
    
        Animal animal1 = new Cat();
        Animal animal2 = new Dog();
        
        animal1.makeSound(); // 调用子类的方法
        animal2.makeSound(); // 调用子类的方法
    }
}

运行以上代码,输出结果为:

Cat meows
Dog barks

在这个示例中,Animal 是抽象类,CatDog 继承自 Animal。通过向上转型,animal1animal2 可以引用 CatDog 对象,并调用它们各自重写的 makeSound() 方法。

5.3 多态的优势和应用场景

多态有以下优势:

  • 灵活性:多态使得程序可以根据实际对象的类型来决定调用哪个方法,从而实现更灵活的编程。
  • 扩展性:通过继承和重写,可以在已有类的基础上扩展出新的类,添加新的功能或修改现有功能。

多态在很多场景中都有应用,其中一些常见的应用场景包括:

  • 封装不同类型的对象:通过将不同类型的对象向上转型为相同的父类或接口,可以使用统一的方式处理它们,提高代码的可读性和可维护性。
  • 实现回调机制:通过接口和多态,可以实现回调机制,即在某个事件发生时调用预先定义好的方法。
  • 实现框架和接口规范:在框架设计中,多态常用于定义抽象类和接口,让不同的子类实现相同的方法,从而达到一致的规范和交互方式。这样可以降低代码的耦合度,增强框架的扩展性。

六、总结

6.1 主要观点总结

  • 封装(Encapsulation)是面向对象编程中的核心概念之一。它将数据和方法封装在一个类中,通过定义访问权限来控制对数据的访问。封装提供了类内部的数据隐藏,使得外部无法直接修改数据,只能通过类提供的方法进行间接操作。这样可以保护数据的完整性和安全性,同时也提高了代码的可读性和可维护性。

  • 继承(Inheritance)是面向对象编程中的一种机制,它允许我们创建一个新的类,从已有的类中继承属性和方法。继承通过建立类之间的关系,可以实现代码的重用和扩展。子类继承父类的属性和方法,并且可以根据需要重写父类的方法或添加新的方法。继承关系可以形成层次结构,使得代码更加结构化和可扩展。

  • 多态(Polymorphism)是面向对象编程中的一个重要特性,指的是同一类型的对象,在不同的情况下表现出不同的行为。多态通过使用父类或接口类型的引用变量引用不同子类对象,以实现灵活、可扩展的程序设计。通过动态绑定机制,可以在运行时根据对象的实际类型确定调用哪个类的方法,实现具体的行为。

6.2 对封装继承、多态的思考

封装、继承和多态是面向对象编程中非常重要的概念,它们有助于实现代码的组织、重用和扩展。以下是我对它们的一些个人思考和看法:

  • 封装可以提供数据的安全性,隐藏内部实现细节,并且通过提供方法来控制对数据的访问。这样可以降低代码的耦合度,增加代码的可维护性。在设计类时,需要合理地划分成员变量和方法的访问权限,遵循封装的原则。

  • 继承有助于代码的重用和扩展。通过继承,可以在已有类的基础上创建新的类,并且继承父类的属性和方法。这样可以减少代码的重复,提高代码的复用性。同时,通过重写父类的方法或添加新的方法,可以在子类中实现特定的功能需求,实现代码的扩展性。

  • 多态是面向对象编程中的一种灵活和可扩展的特性。通过多态,同一个类型的对象可以表现出不同的行为,实现更加灵活的程序设计。多态通过动态绑定机制,在运行时根据对象的实际类型来决定调用哪个类的方法。这样可以实现代码的解耦和可扩展性。

猜你喜欢

转载自blog.csdn.net/pengjun_ge/article/details/132782922