真的了解面向对象吗?

160 阅读3分钟

真的了解面向对象吗?

面向对象的英文为英文Object Oriented,其中Object的牛津词典第一个翻译名词解释为“物体”,所以它的准确翻译应该为“面向物体”,而不是“面向对象”,只不过不知道是谁翻译了这么一个看似“高大上”但是不符合实际的名词。

附上查询结果:

屏幕截图 2024-06-25 215551.png

面向对象应该是一种思想,而不是代码的组织形式,甚至有时候连一个class都没写。

面相对象有三个主要的特点:封装继承多态

  1. 封装:现在我要研究狗,它有叫和咬人的行为,我把它封装成了一个狗的类。

    classclass Dog {  
        constructor(name) {  
            this.name = name;   
        }  
        bark() {  
            console.log(`${this.name} says: Wang! Wang!`);  
        }  
        bite() {  
            console.log(`${this.name} tries to bite!`);  
        }  
    }  
    
  2. 继承:哈士奇是狗的一种,它继承了Dog这个类,于是它继承了父类的行为,它可能时不时就会露出奇怪的表情。

    class Husky extends Dog {  
        constructor(name) {  
            // 调用父类的构造函数,传入name
            super(name);  
        }  
        // 添加一个方法表示哈士奇时不时露出奇怪的表情  
        makeFunnyFace() {  
            // 模拟随机时间露出奇怪表情  
            const shouldMakeFace = Math.random() > 0.5; // 50% 的概率  
            if (shouldMakeFace) {  
                console.log(`${this.name} makes a funny face!`);  
            } else {  
                console.log(`${this.name} looks normal.`);  
            }  
        }  
    }
    
  3. 多态:哈士奇也会叫,但它不是“Wang!Wang!”地叫,而是“Woof! Woof!”,哈士奇有自己的特点,这个就是多态

    class Husky extends Dog {  
        constructor(name) {  
            // 调用父类的构造函数,传入name
            super(name);  
        } 
        // 有自己独特的叫声
        bark() {  
            console.log(`${this.name} says: Woof! Woof!`);  
        } 
        // 添加一个方法表示哈士奇时不时露出奇怪的表情  
        makeFunnyFace() {  
            // 模拟随机时间露出奇怪表情  
            const shouldMakeFace = Math.random() > 0.5; // 50% 的概率  
            if (shouldMakeFace) {  
                console.log(`${this.name} makes a funny face!`);  
            } else {  
                console.log(`${this.name} looks normal.`);  
            }  
        }  
    }
    

    组合是面向对象编程中的一个重要概念,再来看看继承和组合

    继承是为了实现复用,组合也是为了实现复用。继承是is-a的关系,而组合是has-a的关系。

    那么是继承好用一点,还是组合好用一点呢?

    偏向于使用组合而非继承,为什么组合比较好呢?因为继承的耦合性要大于组合,组合更加灵活。继承是编译阶段就决定了关系,而组合是运行阶段才决定关系。组合可以组合多个,而如果要搞多重继承,系统的复杂性无疑会大大增加。

    按照笔者的方式:能用简单的方式解决问题就应该用简单的方式,而不是一着手就是各种面向对象的继承、多态的思想。使用简洁的方式解决问题,然后在考虑性能、代码组织优化等。

    面向对象设计模式和OOP的编程原则

  4. 单例模式

    它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

    class Singleton {  
        // 静态变量存储单例实例  
        static instance;  
      
        // 私有构造函数
        constructor() {  
            if (Singleton.instance) {  
                // 如果实例存在,则抛出错误  
                throw new Error('Error: Instantiation failed: Use Singleton.getInstance() instead of new.');  
            }  
            // 将当前实例赋值给静态变量  
            Singleton.instance = this;  
        }  
      
        // 公开的静态方法,用于获取单例实例  
        static getInstance() {  
            if (!Singleton.instance) {  
                // 如果实例不存在,则创建它  
                new Singleton();  
            }  
            // 返回实例  
            return Singleton.instance;  
        }  
      
        // 如果需要,可以添加更多的静态方法或实例方法  
        // 示例方法  
        somePublicMethod() {  
            console.log('这是单例的一个公共方法');  
        }  
    }  
      
    // 使用示例  
    const instance1 = Singleton.getInstance();  
    const instance2 = Singleton.getInstance();  
      
    // instance1 和 instance2 实际上是同一个对象  
    console.log(instance1 === instance2); // 输出:true  
      
    // 尝试通过new创建实例会抛出错误  
    try {  
        const anotherInstance = new Singleton();  
    } catch (error) {  
        console.error(error.message); // 输出:Error: Instantiation failed: Use Singleton.getInstance() instead of new.  
    }  
      
    // 调用公共方法  
    instance1.somePublicMethod(); // 输出:这是单例的一个公共方法
    

    请注意,这种模式将构造函数设为“私有”(通过不在类外部直接导出它)实现其实不太安全,任何人可通过如下代码破话这个单例。

    Singleton.instance = null;//之后每次都会执行一次new Singleton()
    

    一方面JS本身没有私有属性,那样怎么避免这种情况?

    class Singleton {  
        // 静态变量存储单例实例  
        static instance = null; 
    	// 标记是否已经进行了初始化  
        static initialized = false;  
      
        // 私有构造函数,/初始化代码(如果有的话),注意这里的代码只会在第一次实例化时执行
        constructor() {  
            if (!Singleton.initialized) { 
                // 标记初始化已完成  
                Singleton.initialized = true;
                // 如果实例存在,则抛出错误  
                throw new Error('Error: Instantiation failed: Use Singleton.getInstance() instead of new.');  
            }  
            // 将当前实例赋值给静态变量  
            Singleton.instance = this;  
        }  
      
        // 公开的静态方法,用于获取单例实例  
        static getInstance() {  
            if (!Singleton.instance) {  
                // 如果实例不存在,则创建它  
                new Singleton();  
            }  
            // 返回实例  
            return Singleton.instance;  
        }  
      
        // 如果需要,可以添加更多的静态方法或实例方法  
        // 示例方法  
        somePublicMethod() {  
            console.log('这是单例的一个公共方法');  
        }  
    } 
    
  5. 策略模式

    它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。

    // 定义一个策略接口  
    class Strategy {  
        execute() {  
            throw new Error('Strategy.execute must be implemented by subclass.');  
        }  
    }  
      
    // 实现具体的策略A  
    class ConcreteStrategyA extends Strategy {  
        execute() {  
            console.log('Strategy A is executed.');  
            // 这里是策略A的具体实现  
        }  
    }  
      
    // 实现具体的策略B  
    class ConcreteStrategyB extends Strategy {  
        execute() {  
            console.log('Strategy B is executed.');  
            // 这里是策略B的具体实现  
        }  
    }  
      
    // 上下文(Context)类,它持有一个对Strategy对象的引用  
    class Context {  
        constructor(strategy) {  
            this.strategy = strategy;  
        }  
      
        // 上下文接口,调用策略对象的方法  
        executeStrategy() {  
            this.strategy.execute();  
        }  
      
        // 可以设置一个策略  
        setStrategy(strategy) {  
            this.strategy = strategy;  
        }  
    }  
      
    // 使用策略模式  
    let context;  
      
    // 使用策略A  
    context = new Context(new ConcreteStrategyA());  
    context.executeStrategy(); // 输出: Strategy A is executed.  
      
    // 切换为策略B  
    context.setStrategy(new ConcreteStrategyB());  
    context.executeStrategy(); // 输出: Strategy B is executed.
    
  6. 观察者模式

    它允许对象维护一个依赖它的对象的列表,并在状态发生变化时自动通知它们。

    // 观察者接口  
    class Observer {  
        update(data) {  
            throw new Error('Observer.update must be implemented by subclass.');  
        }  
    }
    // 具体的观察者类  
    class ConcreteObserver {  
        constructor(name) {  
            super();  
            this.name = name;  
        }  
        update(data) {  
            console.log(`${this.name} received: ${data}`);  
        }  
    }  
      
    class Subject {  
        constructor() {  
            this.observers = [];  
        }  
        // 添加观察者  
        attach(observer) {  
            if (observer instanceof Observer) {  
                this.observers.push(observer);  
            }  
        } 
        // 通知所有观察者  
        notify(data) {  
            this.observers.forEach(observer => {  
                observer.update(data);  
            });  
        }  
    }  
    // 使用示例  
    // 创建具体的观察者实例  
    const observer1 = new ConcreteObserver('Observer 1');  
    const observer2 = new ConcreteObserver('Observer 2');  
      
    // 创建主题实例  
    const subject = new Subject();  
      
    // 订阅主题  
    subject.attach(observer1);  
    subject.attach(observer2);  
      
    // 通知观察者  
    subject.notify('Hello, observers!');  
    
  7. 适配器模式

    它允许将一个类的接口转换成客户端所期望的另一个接口形式。使得原本不兼容的类可以一起工作。

    // 目标接口(客户端期望的接口)  
    class Target {  
        request() {  
            throw new Error('Target.request is not implemented.');  
        }  
    }  
      
    // 实现了目标接口的具体类  
    class ConcreteTarget extends Target {  
        request() {  
            return 'Called ConcreteTarget.request()';  
        }  
    }
    
    // 适配者类(需要适配的类)  
    class Adaptee {  
        specificRequest() {  
            return 'Called Adaptee.specificRequest()';  
        }  
    }
    
    // 适配器类,将Adaptee适配到Target接口  
    class Adapter extends Target {  
        constructor(adaptee) {  
            super();  
            this.adaptee = adaptee;  
        }  
      
        // 实现Target接口中的request方法  
        request() {  
            // 调用Adaptee的方法,可能需要进行一些转换  
            const result = this.adaptee.specificRequest();  
            // 可能在此处对result进行一些处理或转换  
            return `Adapter: ${result}`;  
        }  
    }
    
    // 客户端代码  
    function clientCode(target) {  
        // 客户端代码只知道Target接口  
        console.log(target.request());  
    }  
      
    // 创建适配者对象  
    const adaptee = new Adaptee();  
      
    // 创建适配器对象,并将适配者对象传入  
    const adapter = new Adapter(adaptee);  
      
    // 客户端代码通过适配器与适配者交互  
    clientCode(adapter); // 输出:Adapter: Called Adaptee.specificRequest()  
      
    // 客户端也可以直接使用实现了目标接口的具体类  
    const concreteTarget = new ConcreteTarget();  
    clientCode(concreteTarget); // 输出:Called ConcreteTarget.request()
    
  8. 工厂模式

    工厂模式是创建交给一个“工厂”,使用者无需要关心创建细节

  9. 外观/门面模式

    它为子系统中的一组接口提供一个统一的、高层次的接口,使得子系统更容易使用。外观模式隐藏了子系统的复杂性,并使得客户端代码与子系统之间的交互更加简单。

  10. 状态模式

    它允许一个对象在其内部状态改变时改变它的行为。状态模式把对象的每一个状态都封装成一个单独的类,并将请求的处理委托给当前的状态对象。

  11. 代理模式

    它为其他对象提供一个代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,客户端并不直接访问目标对象,而是通过代理对象间接地访问。

  12. 装饰者模式

    它允许你动态地给一个对象添加额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。

总结:

(1) 把共性和特性或者会变和不变的分离出来

(2) 少用继承,多用组合

(3) 低耦高聚

(4) 开闭原则

(5) 单一职责原则

功夫学到最后应该忘掉所有招数,做到心中无法,随心所欲,拈手就来。

参考书籍:高效前端:Web高效编程与优化实践

屏幕截图 2024-06-22 001149.png