在学习 JS 设计模式之前需要了解一些设计模式基础,如果不是 JavaScript 用户可以直接跳到设计模式篇的讲解~
编程语言按照数据类型分为:
动态类型更专注于业务逻辑,缺点是无法保证变量的类型
由于无法保证类型,所以我们可以尝试调用任何对象的任何方法,不用去考虑他是否原本被设计为拥有该方法。这一切都建立在 鸭子类型 的概念上,通俗说法是:“如果他走起来是鸭子,叫起来也是鸭子,那他就是鸭子”
例如:在这种情况下,我们不需要检测实体的类型,如果这个实体具有 singing 这个功能,他就可以被当作是 duck 来使用
var duck = {
function singing(){ ... }
}
var dog = {
function singing(){ ... }
}
多态的含义是:同一个操作在不同的类型上面,可以产生不同的解释,不同的执行效果。
例如这段代码,虽然产生了多态性,但这样的多态性是不让人满意的,如果要加入一个新的动物,你又要修改 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 有时自带这种属性可以利用高阶函数很快实现~
封装一般是指封装数据和封装实现,还有封装类型和封装变化。
许多语言通过 private、protected 等关键字来提供访问权限,但 JS 没有这样的系统,我们只能通过变量的作用域来实现封装特性
var obj = (function(){
var _name = "sven";
return {
getName: function(){
return _name;
}
}
})
上面讲的封装指的是数据层面的封装,这是一种比较狭义的定义
封装的目的是将信息隐藏,封装应该被视为“任何形式的封装“,也就是说,封装还应该隐藏实现细节、设计细节以及隐藏对象的类型等
封装使得对象内部的变化对其他对象而言不可见,对象对他自己的行为负责,其他对象都不关心它的内部实现,这使得对象之间耦合变得松散
封装类是静态语言中重要的一种封装方式。通过抽象类和接口进行,把对象的真实数据隐藏在抽象类或者接口之后。相比对象的类型、客户只需要关注对象的行为
一般的面向对象语言中,类和对象是关键。而在 javascript 这样的原型编程的思想中,类不是必需的,对象是通过克隆另一个对象得来的。
原型模式不单是一种设计模式,还是一种编程泛型。原型模式实现的关键是语言是否拥有 clone 方法,他的优点就是可以减少类的初始化、删除、复刻时产生的大量运算
这里先使用 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 中原型模式这样构建,而且拥有了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