Skip to content

AOP-IoC-OOP-DI

目录


今天在学 Midway 的时候又遇到了 IoC 等相关的概念,故而总结一下


随着项目越来越复杂,代码越来越多的时候,总是会出现各种需要“重复”的功能,以上概念/技术就是在这些环境下出现的解决办法

面向对象编程 OOP

改进:从面向过程编程(POP) 变成面向对象编程(OOP)

优点:

  • 结构更清晰,可读性高
  • 数据更安全,数据不是全局直接能读取到的了
  • 代码更少,复用率更高

需求假设:学校系统有学生和老师,他们都用登陆功能,学生可以查看成绩,老师可以设置成绩

C
// 面向过程实现
struct 老师 {
    用户名
    密码
}

struct 学生 {
    用户名
    密码
    成绩
}

// 学生登陆函数
Bool studentLogin( char *username, char *password) {
    // ...
}
// 老师登陆函数
Bool teacherLogin( char *username, char *password) {
    // ...
}

// 学生查看成绩函数
Int studentCheckScore( char *username, char *password) {
    // ...
}

// 老师设置成绩函数
void teacherSetScore( char *username, char *password, Int score ,char *studentName) {
    // ...
}
C++
// 面向对象实现
class User {
    public:
    char *username;
    char *password;
    Bool login(String username, String password){
        // ...
    }
}

class Student : public User {
    public:
    Int score;
    virtual Int checkScore() {
        // ...
    }
}

class Teacher : public User {
    public:
    virtual void setScore(Int score, String studentName) {
        // ...
    }
}

面向切面编程 AOP

现在我们添加一些需求:在登陆,查询成绩,设置成绩时,日志记录和权限校验

原来的方案会导致大量重复的日记和检查代码,并且如果修改了检查代码,所有涉及的函数都要修改

c++
bool checkPermission() {
    // ...
}

void log(String str) {
    // ...
}

// 面向对象实现
class User {
    public:
    char *username;
    char *password;
    Bool login(String username, String password){
        if(!checkPermission()) {
            log(err);
        }
        log(str);
        // ...
    }
}

class Student : public User {
    public:
    Int score;
    virtual Int checkScore() {
       if(!checkPermission()) {
            log(err);
        }
        log(str);
        // ...
    }
}

class Teacher : public User {
    public:
    virtual void setScore(Int score, String studentName) {
        if(!checkPermission()) {
            log(err);
        }
        log(str);
        // ...
    }
}
C++
// 面向切面编程实现

// 代理类
class UserProxy {
    public:
    User *user;
    bool login(String username, String password){
        if(!checkPermission()) {
            log(err);
        }
        log(str);
        return user->login(username, password);
    }
    bool checkPermission() {
        // ...
    }
    void log(String str) {
        // ...
    }

}
class StudentProxy : public UserProxy {
    public:
    void checkScore() {

    }
}
class TeacherProxy : public UserProxy {
    public:
    void setScore(Int score, String studentName) {

    }
}

// User 类
class User {
    // ...
}
// Student 类
class Student : public User {
    // ...
}
// Teacher 类
class Teacher : public User {
    // ...
}

main(){
    UserProxy *userProxy = new UserProxy(new Student());
}

这里我们使用了代理类的方案实现 AOP,业务函数和检查代码分离,结构更清晰

依赖注入 DI

除了构建代理类,还可以使用依赖注入,更加简单

但是依赖注入的语法在 C++中比较复杂,这里使用 TS 做演示

使用@xxx这种装饰器将检查和日志的函数注入到业务函数上,简介又方便

TypeScript
// 定义日志装饰器
function Log(target: any, methodName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`[LOG] 方法 ${methodName} 被调用,参数:`, args);
        return originalMethod.apply(this, args);
    };
    return descriptor;
}

// 定义权限检查装饰器
function CheckPermission(target: any, methodName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        if (!this.checkPermission()) {
            console.error(`[ERROR] 权限不足,无法执行 ${methodName}`);
            return false;
        }
        return originalMethod.apply(this, args);
    };
    return descriptor;
}

// 定义基础用户类
class User {
    username: string;
    password: string;

    constructor(username: string, password: string) {
        this.username = username;
        this.password = password;
    }

    @Log
    @CheckPermission
    login(username: string, password: string): boolean {
        console.log(`[User] ${username} 登录成功`);
        return true;
    }

    checkPermission(): boolean {
        return true; // 在这里可以加入实际的权限检查逻辑
    }
}

// 定义学生类
class Student extends User {
    @Log
    checkScore() {
        console.log(`[Student] ${this.username} 查询了自己的成绩`);
    }
}

// 定义教师类
class Teacher extends User {
    @Log
    setScore(score: number, studentName: string) {
        console.log(`[Teacher] ${this.username} 给 ${studentName} 设定了分数: ${score}`);
    }
}

// 主程序
const student = new Student("Alice", "12345");
student.login("Alice", "12345"); // 自动调用日志 & 权限检查
student.checkScore();

const teacher = new Teacher("Bob", "admin");
teacher.login("Bob", "admin");
teacher.setScore(90, "Alice");

Ioc 容器

IoC(Inversion of Control)是一种设计模式,它允许应用程序通过依赖注入的方式来获取依赖对象,而不是自己创建。

比如我们现在需要使用控制函数来执行操作,以便将这些功能导出到外部,学生和老师的数据从数据库中获取

每次创建事务都需要创建类,并赋值

js
// 事务管理类
class TransactionManager {
    teacher: Teacher;
    student: Student;
    constructor(teacher: String, student: String) {
        this.teacher = new Teacher(teacher);
        this.student = new Student(student);
        // 从数据库获取学生和老师的数据
        this.student.getData();
        this.teacher.getData();
    }
    // 设置分数
    function setScore( score) {
        teacher.setScore(student, score);
    }
    // 请假
    function leave(reason) {
        teacher.leave(student, reason);
    }
    // 开除
    function dismiss() {
        teacher.dismiss(student);
    }
}
js
// 使用 IoC 容器

@Injectable()
class Teacher {
    // ...
}
@Injectable()
class Student {
    // ...
}

class TransactionManager {

    @Inject('teacher')
    @Inject('student')

    function setScore(score) {
        teacher.setScore(student, score);
    }
    function leave(reason) {
        teacher.leave(student, reason);
    }
    function dismiss() {
        teacher.dismiss(student);
    }
}

IoC 容器的实现这里没有写,在程序加载时,带有@Injectable()装饰器的类会被注册到 IoC 容器中,当需要使用这些类时,可以通过@Inject()装饰器来获取对应的实例,这样就不用手动创建实例了

参考资料

事实上我觉得参考资料写得比我好理解多了,例子也更合适

Copyright © 2022 田园幻想乡 浙ICP备2021038778号-1