Javasript设计模式-工厂模式

181 阅读6分钟

js23种设计模式可以分为三大类:行为型模式、结构型模式、创建型模式。

  • 创建型模式——关注于分离对象的创建和使用
  • 结构型模式——描述如何将类或者对象结合在一起形成更大的结构
  • 行为型模式——不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用

创建型:创建型模式的作用是将模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰 ——就像乐高,相同的零件组合拼接成各种结构,使用不同零件的过程中我们不需要关注这些零件如果被生产出来。

结构型:描述如何将类或者对象组装在一起形成更大的结构-——就像搭积木,可以通过简单积木的组合形成功能更强大更复杂的的结构。

接下来我们去看看具体的创建型模式分类里的工厂模式。

首先说到工厂模式,不得不提到这个与之非常相似的构造器模式,它与工厂模式有很多 相似之处。

构造器

这里我有一个非常简单的场景,我需要记录小组成员信息,那么需要创建一些对象用来描述成员姓名和年龄,这时我写出如下代码:

const aa = {
	name: 'aa',
	age: 25
}

const bb = {
	name: 'bb',
	age: 24
}

可是如果小组成员太多的话,我们不能每一位成员都要这样手动去创建一个对象呀。

还好我们知道,ECMAScript的构造函数就是能创建对象的函数,那么我们有了如下写法:

//构造器
function User(realName, age) {
	this.name = realName;
	this.age = age;
}
const user = new User(realName, age);

那么之后当我们创建对象的时候,只需要进行一下简单的调用。在这个过程中,我们使用构造函数去初始化对象,就是应用了构造器模式。

记不记得我们前面说,创建型模式关注于分离对象的创建和使用。

这里我们可以看到,构造器将 name、age 赋值给对象的过程封装起来,确保每个对象都有这些属性,但同时可以确保name、age取值的不同。我们是不是可以说,构造器本质上是去抽象了每个对象实例的变与不变,不变的是每个对象上都可以有相同的属性,变化的是这些属性的取值可以不同。

好,这是抽象对象的变与不变,那么抽象不同构造函数(类)之间的变与不变,便是我们这一part的主角:工厂模式。

工厂模式

接下来我们添加一需求,上面每位成员我们还需要录入他的工种及工种职责,由于大家的工种不一样,这个时候我们就需要不同的构造函数了:

function Coder(name, age) {
	this.name = name
	this.age = age
	this.career = 'coder'
	this.work = ['写代码','技术分享','取奶茶']
}

function ProductManager(name, age) {
	this.name = name
	this.age = age
	this.career = 'productManager'
	this.work = ['提需求','开会','点奶茶']
}

我们可以发现这两个构造函数都存在变与不变的部分,不变的是他们都拥有name,age,career,work这四个属性,变化的是不同的工种,对应的工作不同。那么接下来我们再判断不同工种应该返回什么样的对象时,可以把这个逻辑写在如下这样的函数里。

function User(name, age, career, work) {
	this.name = name
	this.age = age
	this.career = career
	this.work = work
}

function Factory(name, age, career) {
	let work
	swith(career) {
		case 'coder':
			work = ['写代码','技术分享','取奶茶']
			break
		case 'boss':
			work = ['提需求','开会','点奶茶']
			break
		case 'xxx':
			//其他工种
			break
	return new User(name, age, career, work)	

这样我们就无需创建多个构造函数,只需要调用Factory无脑传参就可以了。

此时这个Factory函数做的事就属于工厂模式,我们可以看到它封装了创建对象的过程,而我们要做的就是无脑传参就可以了。

应用实例

上述例子还是很容易理解的,那我们接下来来一个前端应用实例看看,这里我举一个前端开发中常用的消息提示框的例子。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

这个例子中,我们有几个简单元素:三个按钮和一个消息提示框。需求是,我需要在点击成功按钮时,将提示框的背景色设置为绿色,点击警告按钮时,将背景色设置为黄色,失败时就是红色,这里我通过点击按钮时,切换消息提示框的class来实现简单的html代码如下:

//body:
  <body>
    <div class="wrapper">
      <!-- <div class="modal error">
        <header>sdf</header>
      </div> -->
      <div class="btn-wrap">
        <button data-status="S">成功</button>
        <button data-status="W">警告</button>
        <button data-status="E">失败</button>
      </div>
    </div>
  </body>
  
  //style:
   <style>
    .modal.S {
        background-color: #67c23a;
      }
      .modal.E {
        background-color: #f56c6c;
      }
      .modal.W {
        background-color: #e6a23c;
      }
    </style>

然后是我们给按钮绑定的点击事件的js代码如下:

 const changeStatus = (status: string) => {
    switch (status) {
      case "S":
        oModal.className = "modal success";
        break;
      case "W":
        oModal.className = "modal warning";
        break;
      case "E":
        oModal.className = "modal error";
        break;
      default:
        break;
    }
  };

其中,最主要的是,changeStatus这个函数,这里我们只是做了简单的class切换。

那如果此时,产品经理慢慢走到你面前,说:“我要点击成功的时候,你给我放个五彩斑斓的烟花,点击警告的时候在控制台打印个警告信息,点击错误的时候跳转登陆页~巴拉巴拉吧......”,我们知道,产品经理的嘴是捂不住的,那我们怎么办,难道要在这个changeStatus函数里原地更改每个状态对应要做的事吗?

显然这样做是不好的,明显违背了我们经常强调的开闭原则,那么此时,我们可以借助工厂模式来实现这一需求。

这里其实跟前面那个根据不同工种返回不同构造函数的例子很像,我们可以造一个工厂函数,根据点击的不同按钮传入不同的状态,然后自动去实例化不同状态对应的类,实现如下:

//*枚举状态
export enum MType {
  success = "S",
  warning = "W",
  error = "E",
}
//*创建一个公共类,承载一些公共的方法属性
class Modal {
  status: MType;
  constructor(status: MType) {
    this.status = status;
  }
  
  get className(): string {
    let classStr = "modal ";
    
    switch (this.status) {
      case MType.success:
        classStr += "success";
        break;
      case MType.warning:
        classStr += "warning";
        break;
      case MType.error:
        classStr += "error";
        break;
      default:
        break;
    }
    return classStr;
  }
}

//*不同的状态对应的类分开写,可以在对应类里实现各自不同的功能扩展
class SuccessModal extends Modal {
  constructor() {
    super(MType.success);
  }
  //放一个五彩斑斓的烟花
}
class WarningModal extends Modal {
  constructor() {
    super(MType.warning);
  }
  //控制台打印信息
}
class ErrorModal extends Modal {
  constructor() {
    super(MType.error);
  }
  //跳转登陆页
}

//*工厂函数,通过传入的状态来自动帮我们实例化相应的类
class ModalFactory {
  dom: HTMLElement;
  constructor(dom1: HTMLElement) {
    this.dom = dom1;
  }
  modal: any = null;
  
  create(status: MType) {
    switch (status) {
      case MType.success:
        this.modal = new SuccessModal();
        break;
      case MType.warning:
        this.modal = new WarningModal();
        break;
      case MType.error:
        this.modal = new ErrorModal();
        break;
      default:
        break;
    }
    this.dom.className = this.modal.className;
  }
}

export default ModalFactory;

那么我们使用的时候,就可以在点击事件里这样使用:

const handleClick = (e: Event) => {
    const tar = e.target as HTMLElement;
    const tagName = tar.tagName.toLowerCase();
    if (tagName === "button") {
      const status = tar.dataset.status;
      
      modalFactory.create(status as MType); //通过传入的状态来自动实例化相应的类
      
    }
  };

这样,我们就可以愉快地扩展功能了。

现在我们一起来总结一下什么是工厂模式:工厂模式其实就是将创建对象的过程单独封装。顺便我们还能无脑传参!

美的自动感应洗手机 OXS-2800

知乎

¥99.00

去购买​