面向对象
和面向过程的辨析#
-
面向过程编程(Procedure Oriented Programming)
核心思想:面向过程的核心是以过程或者说函数作为程序设计的基本单元,强调的是解决问题的步骤和顺序。
特点
- 程序结构清晰,通常体现为函数的组合和调用,关注的是如何一步步地执行操作。
- 数据和处理数据的函数通常是分开的,通过函数参数传递数据。
- 优点在于简洁明了,执行效率较高,适用于小型、逻辑相对简单的项目。
- 缺点在于随着程序规模扩大,各部分之间的耦合度会增加,难以复用和维护,不便于管理复杂的系统。
-
面向对象编程(Object Oriented Programming)
核心思想:面向对象编程则是以“对象”为核心,将数据(属性)和操作数据的行为(方法)封装在一起,形成具有独立职责的对象。程序设计围绕着对象及其交互来进行
特点:
- 强调概念抽象,封装、继承和多态这三大特性。
- 封装使得数据隐藏,对外提供接口,增强了安全性和减少耦合度。
- 继承允许子类继承父类的属性和方法,减少代码重复并构建出层次化的类结构。
- 多态使得同一接口可以有不同的表现形式,增加了代码的灵活性和扩展性。
- 优点在于提高了代码的复用性、可维护性和可扩展性,更适应大型软件系统的开发。
- 缺点包括学习成本相对较高,且过度设计可能导致性能损失(尤其是在大量创建对象时)
总结来说,面向过程着重于算法和逻辑控制的流程,而面向对象则更关注现实世界的实体模型和其行为,强调的是利用对象间的交互来解决问题。在实际开发中,两者并不是绝对对立,现代编程实践中往往结合两者的优势,灵活运用在不同的场景中
一个案例#
需求:
要设计一个简单的银行账户管理系统,需求如下:创建账户、存款、取款、查询余额
面向过程编程#
以方法,流程,全局变量来实现解决问题,实现目的
import java.util.HashMap;
import java.util.Map;
public class BankProcess {
// 全局账户字典用于存储账户信息
static Map<String, Double> accounts = new HashMap<>();
public static void main(String[] args) {
createAccount("A001", 1000.0);
deposit("A001", 500.0);
System.out.println("Balance after deposit: " + checkBalance("A001"));
withdraw("A001", 200.0);
System.out.println("Balance after withdrawal: " + checkBalance("A001"));
}
// 创建账户
public static void createAccount(String accountId, double initialBalance) {
accounts.put(accountId, initialBalance);
}
// 存款操作
public static void deposit(String accountId, double amount) {
if (accounts.containsKey(accountId)) {
accounts.put(accountId, accounts.get(accountId) + amount);
}
}
// 取款操作
public static void withdraw(String accountId, double amount) {
if (accounts.containsKey(accountId) && accounts.get(accountId) >= amount) {
accounts.put(accountId, accounts.get(accountId) - amount);
}
}
// 查询余额
public static double checkBalance(String accountId) {
return accounts.getOrDefault(accountId, 0.0);
}
}
面向对象编程#
以对象,对象的属性,对象的行为(方法)来解决问题,实现目的
import java.util.HashMap;
import java.util.Map;
class BankAccount {
private String accountId;
private double balance;
public BankAccount(String accountId, double initialBalance) {
this.accountId = accountId;
this.balance = initialBalance;
}
public static BankAccount getAccount(String accountId) {
return allAccounts.get(accountId);
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public void withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
}
}
public double checkBalance() {
return balance;
}
}
@Test
public void test(){
List<BankAccount> allAccounts = new ArrayList<>();
BankAccount account = new BankAccount("A001", 1000.0);
allAcounts.add()
account.deposit(500.0);
System.out.println("Balance after deposit: " + account.checkBalance());
account.withdraw(200.0);
System.out.println("Balance after withdrawal: " + account.checkBalance());
}
面向对象的三大特性#
封装#
将数据(属性)和操作数据的方法(函数或者方法)绑定在一起,作为一个整体(对象)进行考虑,通过封装,可以隐藏对象的内部细节,仅对外提供公共访问方式,从而提高代码的复用性和安全性。在程序设计中,对象的状态通常由其属性来体现,而行为则通过方法来执行
继承#
继承是一种创建新类的方式,新类可以从已有的类中派生出来,继承父类的属性和方法,同时也可以扩展新的属性和方法.这样可以提高代码的重用性,使得类和类之间产生了层次结构,便于系统的维护和扩展
多态#
多态,指为不同数据类型的实体提供统一的接口,以Java语言为例,多态有以下要素
- 继承,多个子类继承/实现了一个父类
- 重写,多个子类重写了父类的同名方法,有不同实现
- 父类引用指向子类对象(默认调用子类的重写方法)
//1.继承,父类
public class Animal {
public void shout();
}
//1.继承,一个子类
@Slf4j
public class Dog extends Animal {
//2.重写了父类的方法
@Override
public void shout() {
log.info("wanwan");
}
}
@Slf4j
public class Cat extends Animal {
@Override
public void shout() {
log.info("mimi");
}
}
@Test
public void test() {
Animal ani = new Dog();//3.父类引用指向子类对象
ani.shout();//3.调用子类dog重写后的方法 wanwan
}
上述代码展示了多态,支持多态就意味着,所有需要具体子类的地方都可以用父类笼统概括,最直观的莫过于,同一个方法用父类做形参,可以传入不同子类实现不同的功能,降低了耦合度,提高了扩展性
面向对象的五大原则#
单一职责原则(Single Responsibility Principle)#
一个类或者模块应该有且只有一个改变的理由,即一个类应该只有一个职责,当且仅当它负责的功能发生变化时才需要修改
开放封闭原则(Open/Close Principle)#
类模块应该是可扩展的,但不可修改,在不修改已有代码的情况下,能够通过扩展模块的行为来添加新的功能
为什么叫开放封闭原则?
对扩展开放,对修改关闭
里氏替换原则(Liskov Substitution Principle)#
子类型必须能够替换掉它们的基类型。这意味着在继承体系中,子类应当可以在任何需要基类出现的地方无差别地替代基类,而且不会破坏原有的正确性
为什么叫里氏替换原则?
里斯科夫(Liskov)女士在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出来:"继承必须确保超类所拥有的性质在子类中仍然成立"
接口隔离原则(Interface Segregation Principle)#
接口隔离原则建立在使用接口的情况下,客户端不应该被迫使用对其无用的方法或者功能,一个类对另一个类的依赖应该建立在最小的接口上
实践起来就是接口不应该过于臃肿和冗余,依赖一个臃肿冗余的接口,不如把这个接口拆成多个,来按需依赖
依赖倒置原则(Dependence Inversion Principle)#
- 高层模块(如业务逻辑层)不应该依赖于底层模块(如数据访问层),二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒置的核心是"面向接口编程"
- 抽象对比细节,要稳定,以抽象为基础搭建的框架比细节的框架要稳定的多
依赖倒置原则的依赖倒置体现在哪里?
在传统的自底向上设计中,高层模块(如业务逻辑层)会直接依赖底层模块(如数据访问层)形成一种具体依赖具体的依赖关系
个人理解的倒置,应该和高层,底层没什么关系,是一种理念上的倒置,即由依赖具体转变为依赖抽象