JavaScript 的面向对象基础,设计模式中的原型模式(设计模式与开发实践 P2)

文章目录

    • 1.1 动态类型语言和鸭子类型
    • 1.2 多态
    • 1.3 封装
      • 封装数据
      • 封装实现
      • 封装类型
    • 1.4 原型模式和基于原型继承的 JavaScript 对象系统
      • C# 原型模式
      • JS 原型模式

在学习 JS 设计模式之前需要了解一些设计模式基础,如果不是 JavaScript 用户可以直接跳到设计模式篇的讲解~

1.1 动态类型语言和鸭子类型

编程语言按照数据类型分为:

  • 动态类型语言
  • 静态类型语言

动态类型更专注于业务逻辑,缺点是无法保证变量的类型

由于无法保证类型,所以我们可以尝试调用任何对象的任何方法,不用去考虑他是否原本被设计为拥有该方法。这一切都建立在 鸭子类型 的概念上,通俗说法是:“如果他走起来是鸭子,叫起来也是鸭子,那他就是鸭子”

例如:在这种情况下,我们不需要检测实体的类型,如果这个实体具有 singing 这个功能,他就可以被当作是 duck 来使用

var duck = {
	function singing(){ ... }
}

var dog = {
	function singing(){ ... }
}

1.2 多态

多态的含义是:同一个操作在不同的类型上面,可以产生不同的解释,不同的执行效果。

例如这段代码,虽然产生了多态性,但这样的多态性是不让人满意的,如果要加入一个新的动物,你又要修改 makeSound 这个函数,到最后很可能变的巨大!

多态的思想是,将 做什么、谁去做、怎样做 分离开,也就是把 不变的事情和可变的事情 分开,使得程序是可生长的,又符合 开放-封闭 原则

var makeSound = function(animal) {
	if (animal instanceof Duck) {
		console.log("噶");
	} else if (animal instanceof Dog) {
		console.log("汪");
	}
}

var Duck = function() {}
var Dog = function() {}

makeSound(new Duck())
makeSound(new Dog())

让我们试试改写代码,这是封装的不变的部分:

var makeSound = function(animal) {
	animal.sound();
}

接下来完成可变的部分,如果后续要添加其他动物,只需要追加代码,而不用改变 makeSound 了:

var Duck = function() {}
Duck.prototype.sound = function() {
	console.log("噶");
}

makeSound(new Duck())

如果你使用 Java,按照上面的写法你会发现一个问题:

public class Duck{
	public void makeSound(){
		System.out.println("噶");	
	}
}

public class AnimalSound{
	public void makeSound(Duck duck) {
		duck.makeSound();
	}
}

public class Main{
	public static void main(){
		AnimalSound sound = new AnimalSound();
		Chicken chicken = new Chicken();
		sound.makeSound(chicken);
	}
}

sound.makeSound 固定接收了 Duck 类型参数,没有办法让他接受动态类型,这就需要继承来解决问题了,通过 abstract 抽象类,让 Duck 和 Dog 都继承 Animal 类型~

public abstract class Animal{
	abstract void makeSound();
}

public class Chicken extends Animal...

让我们再来一段代码理解 js 和 java 中的区别:

var googleMap = {
	show: function() { ... }
}
var baiduMap = {
	show: function() { ... }
}

var renderMap = function (type) {
	if (type instanceof baiduMap) { ... }
	else if (type instanceof googleMap) { ... }
}

如果要加一个 bing 地图,此时又要修改 renderMap,在 JS 中则这样改写即可,可以看到 JS 自带了这种动态类型转换的属性,因为 JS 只关注实现,不再关注类型细节了:

var renderMap = function(map) {
	if (map.show instanceof Function) map.show();
}

renderMap(googleMap);
renderMap(baiduMap);

后续的许多设计模式都离不开多态,而 JS 有时自带这种属性可以利用高阶函数很快实现~

1.3 封装

封装一般是指封装数据和封装实现,还有封装类型和封装变化。

封装数据

许多语言通过 private、protected 等关键字来提供访问权限,但 JS 没有这样的系统,我们只能通过变量的作用域来实现封装特性

var obj = (function(){
	var _name = "sven";
	return {
		getName: function(){
			return _name;
		}
	}
})

封装实现

上面讲的封装指的是数据层面的封装,这是一种比较狭义的定义

封装的目的是将信息隐藏,封装应该被视为“任何形式的封装“,也就是说,封装还应该隐藏实现细节、设计细节以及隐藏对象的类型

封装使得对象内部的变化对其他对象而言不可见,对象对他自己的行为负责,其他对象都不关心它的内部实现,这使得对象之间耦合变得松散

封装类型

封装类是静态语言中重要的一种封装方式。通过抽象类和接口进行,把对象的真实数据隐藏在抽象类或者接口之后。相比对象的类型、客户只需要关注对象的行为

1.4 原型模式和基于原型继承的 JavaScript 对象系统

一般的面向对象语言中,类和对象是关键。而在 javascript 这样的原型编程的思想中,类不是必需的,对象是通过克隆另一个对象得来的。

原型模式不单是一种设计模式,还是一种编程泛型。原型模式实现的关键是语言是否拥有 clone 方法,他的优点就是可以减少类的初始化、删除、复刻时产生的大量运算

  • 所有数据都是对象
  • 对象不是通过实例化类,而是通过找到对象作为原型并克隆他

C# 原型模式

这里先使用 C# 举例,下面是类的实现:

using System;

// 基类
public abstract class Shape
{
    public int X { get; set; }
    public int Y { get; set; }

    public abstract Shape Clone();
}

// Circle 派生类
public class Circle : Shape
{
    public int Radius { get; set; }

    public override Shape Clone()
    {
        return (Shape)MemberwiseClone();
    }
}

下面是原型模式的应用,可以看到通过新的数据 clonedCircle 通过克隆得来:

class Program
{
    static void Main(string[] args)
    {
        Circle circle = new Circle();
        circle.X = 10;
        circle.Y = 20;
        circle.Radius = 30;

        Circle clonedCircle = (Circle)circle.Clone();
        Console.WriteLine($"Circle: X={clonedCircle.X}, Y={clonedCircle.Y}, Radius={clonedCircle.Radius}");

        Console.ReadLine();
    }
}

JS 原型模式

在 JS 中原型模式这样构建,而且拥有了2个新的特性:

  1. 对象会记住他的原型
  2. 如果对象无法响应某个请求,对象会尝试委托原型进行响应
// 原型对象
const shapePrototype = {
  x: 0,
  y: 0,
  clone() {
    // 创建一个新对象,并使用原型对象的属性进行初始化
    const clone = Object.create(this);
    return clone;
  }
};

// Circle 构造函数
function Circle(radius) {
  this.radius = radius;
}

// 将 shapePrototype 设置为 Circle 构造函数的原型
Circle.prototype = shapePrototype;

// 创建 Circle 对象的实例
const circle = new Circle(10);
circle.x = 20;
circle.y = 30;

// 克隆 Circle 对象
const clonedCircle = circle.clone();

console.log(clonedCircle.x);  // 输出: 20
console.log(clonedCircle.y);  // 输出: 30
console.log(clonedCircle.radius);  // 输出: 10

你可能感兴趣的:(设计模式,javascript,设计模式,原型模式)