真的了解面向对象吗?
面向对象的英文为英文Object Oriented,其中Object的牛津词典第一个翻译名词解释为“物体”,所以它的准确翻译应该为“面向物体”,而不是“面向对象”,只不过不知道是谁翻译了这么一个看似“高大上”但是不符合实际的名词。
附上查询结果:
面向对象应该是一种思想,而不是代码的组织形式,甚至有时候连一个class都没写。
面相对象有三个主要的特点:封装、继承和多态。
-
封装:现在我要研究狗,它有叫和咬人的行为,我把它封装成了一个狗的类。
classclass Dog { constructor(name) { this.name = name; } bark() { console.log(`${this.name} says: Wang! Wang!`); } bite() { console.log(`${this.name} tries to bite!`); } } -
继承:哈士奇是狗的一种,它继承了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.`); } } } -
多态:哈士奇也会叫,但它不是“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的编程原则
-
单例模式
它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
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('这是单例的一个公共方法'); } } -
策略模式
它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。
// 定义一个策略接口 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. -
观察者模式
它允许对象维护一个依赖它的对象的列表,并在状态发生变化时自动通知它们。
// 观察者接口 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!'); -
适配器模式
它允许将一个类的接口转换成客户端所期望的另一个接口形式。使得原本不兼容的类可以一起工作。
// 目标接口(客户端期望的接口) 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() -
工厂模式
工厂模式是创建交给一个“工厂”,使用者无需要关心创建细节
-
外观/门面模式
它为子系统中的一组接口提供一个统一的、高层次的接口,使得子系统更容易使用。外观模式隐藏了子系统的复杂性,并使得客户端代码与子系统之间的交互更加简单。
-
状态模式
它允许一个对象在其内部状态改变时改变它的行为。状态模式把对象的每一个状态都封装成一个单独的类,并将请求的处理委托给当前的状态对象。
-
代理模式
它为其他对象提供一个代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,客户端并不直接访问目标对象,而是通过代理对象间接地访问。
-
装饰者模式
它允许你动态地给一个对象添加额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。
总结:
(1) 把共性和特性或者会变和不变的分离出来
(2) 少用继承,多用组合
(3) 低耦高聚
(4) 开闭原则
(5) 单一职责原则
功夫学到最后应该忘掉所有招数,做到心中无法,随心所欲,拈手就来。
参考书籍:高效前端:Web高效编程与优化实践