拓展
面向对象三要素
继承 - 抽离公共代码,实现代码复用 -- super
封装 - 高内聚、低耦合 -- 可见性修饰符 public、protected、private
多态 - 更好的拓展性 -- 重写和重载(一个函数多参数类型)
设计原则汇总
单一职责原则
- 每个程序都做好一件事情;
- 功能多了就要做好拆分;
- 每个部分保持互相独立;
- 总结就是从功能方面来说某个函数只负责一个功能;
开放封闭原则
- 对拓展开放;
- 对修改封闭;
- 需求发生变化时,通过拓展来解决,而非改动;
依赖倒置原则
- 依赖需要抽象一点,不要太具体化;
- 相对依赖具体的类,应该尝试抽象一点的依赖接口;
function fn(){p: Student} // 依赖具体的类 -- 不建议 不能太具体化
function fn(){p: IPerson} // 依赖接口 不能太具体化
创建对象对的三种方法
var newObject = {};
var newObject = Object.create( Object.prototype );
var newObject = new Object();
//上面的三中方式创建出来的新对象等同,Object.create(null)是一个简单的对象,不具有其他任何的属性。
创建属性的四种方式
let newObject = { _name:123 }
newObject.normal = "Hello World";
newObject["normal1"] = "Hello World";
Object.defineProperty( newObject, "defineproperty_key", {
value: "defineproperty_value",
writable: true,
enumerable: true,
configurable: true
});
newObject.defineproperty_key = 333
/**
* Object.defineProperties(obj, props)
* 用途:在对象上定义多个属性或者修改多个原有属性
* 返回值:修改后的目标对象
* @params obj 新增或要修改的目标对象
* @params props 属性对象,其属性值为属性描述符(数据属性描述符和访问器属性描述符)
* @params 仅可以修改原有的普通属性,不能修改用defineProperties或defineProperty定义的属性
* @notice 用defineProperties定义的数据属性默认是不可枚举的,即enumerable: false
* @notice 用defineProperties和defineProperty定义的访问器属性对应的setter/getter等特性(描述符)均默认为undefined
* @notice 只能修改对象的原有属性,不能修改用defineProperties定义的属性,否则会属性重定义的错误
* @notice defineProperties定义的属性对应的enumerable/configurable/writable等特性(描述符)均默认为false
*/
Object.defineProperties( newObject, {
"someKey": {
value: "Hello World",
writable: true,
enumerable: true
},
"anotherKey": {
value: "Foo bar",
// writable: false
}
});
console.log(newObject) // 结果为{_name: 123, someKey: "Hello World",anotherKey: "Foo bar"}
console.log(Object.keys(newObject)) //结果为["_name"]
/*
{
_name: 123,
normal: 'Hello World',
normal1: 'Hello World',
defineproperty_key: 333,
someKey: 'Hello World'
}
[ '_name', 'normal', 'normal1', 'defineproperty_key', 'someKey' ]
*/
工厂模式简介
- 工厂模式是用来创建对象对的一种最常用的设计模式,通过将逻辑封装在函数内部,不暴露具体的逻辑来实现创建对象的方式;
- 工厂模式就是简化了对象的构建,将构建对象的过程封装在工厂类中,当需要创建对象的时候只需要向工厂发出产生对象的指令即可
相关分析
适用于
- 将new操作简单封装,遇到new的时候应该优先考虑是否使用工厂模式;
- 需要依据具体环境创建不同的实例,这些实例都有相同的行为,此时可以考虑使用工厂模式;
- 不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从多个子系统中进行挑选,可以考虑使用工厂模式;
优点
- 不用关心创建过程,只需要关注于创建的结果即可;
- 构造函数和创建者分离,符合设计模式中的
开放封闭原则
; - 拓展性高,想要增加一个数据类,只需要拓展一个工厂类即可;
- 结构清晰,有效的封装变化。产品类的实例化有时候是复杂和多变的,通过工厂模式将产品的实例化发
缺点
- 添加新数据类时,可能需要更改工厂类的原始逻辑,导致系统复杂度增加;
- 考虑到系统的可拓展性,需要引入抽象层进行相关提取和复用,增加了系统的抽象性和理解难度;
实际应用
JQuery中的$()
- jQuery中的$()就是一个工厂函数,根据传入参数的不同执行相应的逻辑;
class jQuery {
constructor(selector){
super(selector)
}
add() {
// ...
}
// ...
}
window.$ = function(selector) {
return new jQuery(selector)
}
vue的异步组件
- 当在组件拆分的时候会遇到需要被渲染的时候才会触发渲染的组件时就可以用vue提供的工厂函数方式声明的组件进行渲染,vue只有在这个组件需要被渲染的时候才会触发该工厂函数,并且将结果缓存起来供未来重新渲染;
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
React的createElement()
// React.createElement(
// type,
// [props],
// [...children]
// )
let profile = React.createElement("div",null,
React.createElement("img",{src: "avatar.png", className: "profile"}),
React,createElement("h3",null,[user.firstName,user.lastName].join(" "))
);
class Vnode(tag,attrs,children){
// 具体实现逻辑...
}
React.createElement = function(tag,attrs,children) {
return new Vnode(tag,attrs,childred)
}
拓展
每个 JSX 元素只是调用 React.createElement(component, props, ...children)
的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。
//JSX 编写
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Hello toWhat="World" />);
//不使用 JSX 创建组件
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(Hello, {toWhat: 'World'}, null));
// 不使用 JSX 创建普通标签并提取
const e = React.createElement;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(e('div', null, 'Hello World'));
工厂模式划分
简单工厂模式
简介
简单工厂模式
又称为静态工厂模式
,主要用来创建创建同一类对象,所有细节都依靠一个工厂完成,由一个工厂对象决定某一种
产品对象类的实例,开发者不需要关注这些对象到底依赖哪个基类,只能作用于对象数量较少,对象的创建逻辑不复杂的情况;
其最大优点在于实现对象的创建和对象的使用分离
,将特定类型
的对象交付给专门的工厂实现,但最大的缺点就是不够灵活,当需要添加新类型的对象时需要修改工厂类的相关逻辑,而且代码数量较多时逻辑处理会相对较复杂;
简单工厂只能作用于创建的对象数量较少,对象的创建逻辑不复杂时使用
简单工厂模式是用一个统一的工厂类来创建所有的对象
简单工厂模式可以创建相似对象,把多个相似类(对象)的相似部分提取出来,不相似部分针对性处理即可
案例
- 项目中通过用户权限来渲染不同的页面,每个权限的用户都有特定的不一样得到页面功能,可以根据权限实例化用户,可以在不同权限等级用户的构造函数里保存用户可以看得到的页面信息;
- 代码实现
class User {
constructor({name,viewPage}){
this.name = name;
this.viewPage = viewPage;
}
//暂时考虑将每个实例都采用同一构造函数 正常都需要特定的构造函数来特定化实现
// static关键字将简单工厂封装到user类的静态方法中;
static getInstance(role){
switch(role){
case 'superAdmin':
return new User({name:'超级管理员',viewPage:['首页', '通讯录', '用户管理', '应用数据', '权限管理']})
break;
case 'admin':
return new User({name:'管理员',viewPage:['首页', '通讯录', '用户管理']})
break;
case 'generalUser':
return new User({name:'普通用户',viewPage:['首页', '通讯录']})
break;
default:
throw new Error('Parameter error, Optional parameter: superDdmin、admin、generalUser')
}
}
}
const superAdmin = User.getInstance('superAdmin'),
admin = User.getInstance('admin'),
generalUser = User.getInstance('generalUser')
// normalUser = User.getInstance('normalUser')
// Error: Parameter error, Optional parameter: superDdmin、admin、generalUser
console.log(superAdmin)
/*
User {
name: '超级管理员',
viewPage: [ '首页', '通讯录', '用户管理', '应用数据', '权限管理' ]
}
*/
function getFunction(path, params) { // get请求
console.log(path, params)
}
function postFunction(path, params) { // post请求
console.log(path, params)
}
function putFunction(path, params) { // put请求
console.log(path, params)
}
function ajaxSend(type, path, params) { // ajax发送请求
switch (type) {
case 'post': {
postFunction(path, params)
break;
};
case 'put': {
putFunction(path, params)
break;
};
default:
getFunction(path, params)
}
}
ajaxSend('get', 'path', 'params')
工厂方法模式(Factory Method)
简介
工厂方法模式是对简单工厂模式的进一步优化,在该模式中,不再提供一个传统的工厂类来创建所有的对象,而是
针对不同得到对象提供不同的工厂
。的本质是将实际创建对象的工作推迟到子类中,这样核心类就变成了抽象类。
工厂方法模式对的本意是将实际创建对象的工作推迟到子类中,这样核心就变成了抽象类 可以把工厂方法看做是一个实例化对象的工厂,只专注于实例化对象这一件事 工厂方法模式是针对不同的对象提供不同的工厂,每个对象都有一个与之对应的工厂,解决了简单工厂模式下添加新类需要修改工厂函数的问题
案例
class User {
// 专注于实例化对象
constructor(name,viewPage) {
// new.target指向[class UserFactory extends User] 即new.target === UserFactory
// new.target指向直接被new执行的构造函数
if(new.target === User) throw new Error('抽象类不能实例化');
this.name = name;
this.viewPage = viewPage;
}
}
class UserFactory extends User {
// 继承于专注的工厂方法的对象
constructor(name,viewPage){
super(name, viewPage)
}
// 继承后专注于实例化权限对象
create(role) {
switch (role) {
case 'superAdmin':
return new UserFactory('超级管理员',['首页', '通讯录', '用户管理', '应用数据', '权限管理'])
break;
case 'admin':
return new UserFactory('管理员',['首页', '通讯录', '用户管理'])
break;
case 'generalUser':
return new UserFactory('普通用户',['首页', '通讯录'])
break;
default:
throw new Error('权限参数错误, 可选参数:superAdmin、admin、user')
}
}
}
let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
let admin = userFactory.create('admin');
let user = userFactory.create('generalUser');
console.log(superAdmin,'superAdmin=========')
console.log(admin,'admin=========')
console.log(user,'user=========')
// UserFactory {
// name: '超级管理员',
// viewPage: [ '首页', '通讯录', '用户管理', '应用数据', '权限管理' ]
// } superAdmin=========
// UserFactory { name: '管理员', viewPage: [ '首页', '通讯录', '用户管理' ] } admin=========
// UserFactory { name: '普通用户', viewPage: [ '首页', '通讯录' ] } user=========
const ajaxType = {
get(path,params){
// ...
console.log(path,'==get==',params)
},
post(path,params){
// ...
console.log(path,'==post==',params)
},
put(path,params){
// ...
console.log(path,'==put==',params)
}
}
function $ajax(way,path,params) {
try {
ajaxType[way](path,params)
} catch(err) {
console.error(err,`${way} 方法匹配失败`)
}
}
$ajax('get','/api/userinfo',{token:'123456asdfgh'})
$ajax('delete','/api/article',{id:'asddad33'})
//当需要进行拓展的时候就可以直接在FunctionFactory.prototype上进行拓展,无需修改工厂的方法,符合OCP(Open-Closed Principle,开放-封闭原则)原则
function FunctionFactory(role) {
if (!(["admin", "developer"].indexOf(role) > -1)) {
throw new Error("参数只能为 admin 或 developer");
}
// 安全的工厂方法
if (this instanceof FunctionFactory) {
return this[role]();
}
return new FunctionFactory(role);
}
FunctionFactory.prototype.show = function () {
var str = "是一个" + this.role + ", 权限:" + this.permissions.join(", ");
console.log(str);
};
FunctionFactory.prototype.admin = function (permissions) {
this.role = "管理员";
this.permissions = [
"设置",
"删除",
"新增",
"创建",
"开发",
"推送",
"提问",
"评论",
];
};
FunctionFactory.prototype.developer = function (permissions) {
this.role = "开发者";
this.permissions = ["开发", "推送", "提问", "评论"];
};
var xm = FunctionFactory("admin");
xm.show();
var xh = FunctionFactory("developer");
xh.show();
var xl = FunctionFactory("guest");
xl.show();
优点
- 增加新产品时,无需修改已存在的代码;
- 只暴露工厂类,对具体实现封装在内部,使用时无需关注内部实现原理;
缺点
- 新增新类型的产品时,还是需要新增具体实现和提供具体的工厂类,一定程度上增加了系统的复杂度,带来额外开销;
- 每个工厂只生产一种产品,会导致存在大量的工厂类,大大增加系统的维护成本和运行开销;
抽象工厂模式(Abstract Factory Pattern)
简介
抽象工厂模式是围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂,这种类型的设计模式属于创建型模式,是创建对象的最佳方式。
在抽象工厂中,类簇一般由父类定义,并在父类中定义一些抽象方法,通过抽象工厂让子类继承父类;
抽象工厂其实是实现子类继承父类的方法,相比于前两者模式,该模式并不直接生成实例,而是用于对产品类簇的创建
抽象工厂只留对外得到口子,不做事,留给外界进行覆盖重写
抽象类更像是一种规则,就和你开个银行卡,就必须携带身份证、手机等;创建时没有任何属性,原型上的方法也不能用,否则就会报错,但是在继承上却很有用,因为定义了一种类,并且定义了该类所必备的方法,如果子类中没有重写这些方法,当调用时能找到这些方法便会报错(常规的方法未定义报错XXX is not a function
,抽象工厂类的报错则是在定义基类是做的兼容报错处理提示)
实例
//抽象工厂方法
var VehicleFactory = function(subType, superType) {
//判断抽象工厂中是否有该抽象类
if (typeof VehicleFactory[superType] === 'function') {
//缓存类
function F() {};
//继承父类属性和方法
//相比于直接通过原型继承,new关键字的方式继承父类对象属性和原型上的拓展;
F.prototype = new VehicleFactory[superType]();
//将子类constructor指向子类
subType.constructor = subType;
//子类原型继承父类
subType.prototype = new F();
} else {
//不存在该抽象类则抛错
throw new Error('未创建该抽象类');
}
};
//小汽车抽象类
VehicleFactory.Car = function() {
this.type = 'car';
}
VehicleFactory.Car.prototype = {
getPrice: function() {
return new Error('抽象方法不能调用');
},
getSpeed: function() {
return new Error('抽象方法不能调用');
}
};
//公交车抽象类
VehicleFactory.Bus = function() {
this.type = 'bus';
}
VehicleFactory.Bus.prototype = {
getPrice: function() {
return new Error('抽象方法不能调用');
},
getPassengerNum: function() {
return new Error('抽象方法不能调用');
}
}
//抽象与实现
//宝马汽车子类
var BMW = function(price, speed) {
this.price = price;
this.speed = speed;
};
//抽象工厂实现对Car抽象类的继承
VehicleFactory(BMW, 'Car');
BMW.prototype.getPrice = function() { //重写方法
return this.price;
}
BMW.prototype.getSpeed = function() { //重写方法
return this.speed;
};
//宇通公交车子类
var YUTONG = function(price, passenger) {
this.price = price;
this.passenger = passenger;
};
//抽象工厂实现对BUS抽象类的继承
VehicleFactory(YUTONG, 'Bus');
YUTONG.prototype.getPrice = function() { //重写方法
return this.price;
}
YUTONG.prototype.getPassengerNum = function() { //重写方法
return this.passenger;
};
//实例化类
var myBMW = new BMW('100w', '1000km/h');
console.log(myBMW.getPrice(), myBMW.getSpeed(), myBMW.type);
//未重写基类方法时则会提示 抽象方法不能调用 的错误信息
class User {
constructor(type) {
if (new.target === User) {
throw new Error('抽象类不能实例化!')
}
this.type = type;
}
}
class UserOfWechat extends User {
constructor(name) {
super('wechat');
this.name = name;
this.viewPage = ['首页', '通讯录', '发现页']
}
}
class UserOfQq extends User {
constructor(name) {
super('qq');
this.name = name;
this.viewPage = ['首页', '通讯录', '发现页']
}
}
class UserOfWeibo extends User {
constructor(name) {
super('weibo');
this.name = name;
this.viewPage = ['首页', '通讯录', '发现页']
}
}
function getAbstractUserFactory(type) {
switch (type) {
case 'wechat':
return UserOfWechat;
break;
case 'qq':
return UserOfQq;
break;
case 'weibo':
return UserOfWeibo;
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
let WechatUserClass = getAbstractUserFactory('wechat');
let QqUserClass = getAbstractUserFactory('qq');
let WeiboUserClass = getAbstractUserFactory('weibo');
let wechatUser = new WechatUserClass('微信小李');
let qqUser = new QqUserClass('QQ小李');
let weiboUser = new WeiboUserClass('微博小李');
在工厂模式的基础上进行更高层次的抽象,根据共同的用途或主题来抽象出一个最高层基础工厂类,其他具有相似行为的工厂类将继承于此工厂类
当需要在代码的其余所有部分通过屏蔽较为复杂的对象创建方法来简化某些特定对象的创建过程时,使用工厂模式很适合
当需要从现有代码中的多个类中,根据这些类之间共有的目的或通用的主题,创建出一个额外的抽象层,以降低应用程序的其余开发工作的复杂性时,使用抽象工厂模式很适合
总结
- 简单工厂模式:你给工厂什么,工厂就给你生产什么;
- 工厂方法模式:类似外包公司,需求交接时先看能不能做,能做就直接接(已有工厂类可以实现),不能做就找其他厂(重新拓展新工厂类);
- 抽象工厂模式:工厂接了一个订单但是做不了,上级集团就新建一个工厂来专门加工某项产品,类似于淘宝、京东、拼夕夕这样的工厂;
- 抽象工厂模式创建出的结果不一定是一个真实的对象实例,而是一个类簇,它定制了类的结构,通过子类继承来实现抽象类簇的封装提取,这也是区别于简单工厂模式创建单一对象、工厂方法模式创建多类对象;
- 抽象工厂模式在进行类簇定义时需要给子类预留需要覆盖的实例化方法,并且进行相应的容错处理
throw new Error(该类是抽象类,不能进行实例化)
,而非普通的XXX is not a function
,
继承函数封装
/*创建extend函数为了程序中所有的继承操作*/
//subClass:子类 superClass:超类
function extend(subClass,superClass) {
//1,使子类原型属性等于父类的原型属性
//初始化一个中间空对象,目的是为了转换主父关系
var F = function () {};
F.prototype = superClass.prototype;
//2, 让子类继承F
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
//3,为子类增加属性 superClass ==》原型链的引用
subClass.superClass = superClass.prototype;
//4,增加一个保险,就算你的原型类是超类(Object)那么也要把你的构造函数级别降下来
if (superClass.prototype.constructor == Object.prototype.constructor) {
superClass.prototype.constructor = superClass;
}
}
设计原则分析
- 工厂和类分离,达到解耦的目的;
- 可以拓展多个类(派生类或平行的类);
- 工厂的创建逻辑也可以进行自由拓展;
案例分析
拓展性不好,当需要添加新类型时需要修改Switch部分,必定会修改工厂类的源代码
var TravelTeam = function(){};
TravelTeam.prototype = {
register : function(type){
var person;
switch(type){
case "student":
person = new Student();
break;
case "teacher":
person = new Teacher();
break;
default:
person = new Doctor();
break;
}
return person;
}
}
var team = new TravelTeam();
team.register('student');
工厂方法模式版
抽离队员类型,降低耦合度,当需要添加新类型时只需要修改抽离出来的部分即可;
var TeamFactory = {
createTeam:function(type){
var person;
switch(type){
case "student":
person = new Student();
break;
case "teacher":
person = new Teacher();
break;
default:
person = new Doctor();
break;
}
return person;
}
}
var TravelTeam = function(){};
TravelTeam.prototype = {
register:function(type){
var person = TeamFactory.createTeam(type);
return person;
}
}
var team = new TravelTeam();
team.register('student');
dialog封装
工厂方法模式 - 版本一
var Dialog = (function () {
var createNotice = function () {
return "<div>notice</div>";
};
var createToast = function () {
return "<div>toast</div>";
};
var createWarnin = function () {
return "<div>warnin</div>";
};
var Dialog = function () {
this.element = "";
this.name = "";
this.show = function () {
console.log(this.name + " is show -> " + this.element);
};
};
return {
factory: function (arg) {
var _dialog;
if (arg === "notice") {
_dialog = new Dialog();
_dialog.element = createNotice();
_dialog.name = "notice";
} else if (arg === "toast") {
_dialog = new Dialog();
_dialog.element = createToast();
_dialog.name = "toast";
} else if (arg === "warnin") {
_dialog = new Dialog();
_dialog.element = createWarnin();
_dialog.name = "warnin";
}
return _dialog;
},
};
})();
var notice = Dialog.factory("notice");
var toast = Dialog.factory("toast");
var warnin = Dialog.factory("warnin");
toast.show(); //toast is show -> <div>toast</div>
notice.show(); //notice is show -> <div>notice</div>
warnin.show(); //warnin is show -> <div>warnin</div>
工厂方法模式 - 版本二
定义一个创建对象的接口,让子类决定将哪一个类实例化,工厂方法模式让一个类的实例化延迟到其子类,对所有的入口拆成独立的工厂类,简单工厂模式版在新增时需要修改工厂类,违反了开关原则;
工厂方法模式保证新增时只做添加不做修改;项目足够大时可以将Factory和Dialog对的子类单独拆分成文件进行维护;
var Dialog = function(){
this.show = function(){
console.log(this.name + ' is show -> ' + this.element);
}
};
Dialog.createNotice = function(){
var _dialog = new Dialog();
_dialog.element = '<div>notice</div>';
_dialog.name = 'notice';
return _dialog;
};
Dialog.createToast = function(){
var _dialog = new Dialog();
_dialog.element = '<div>toast</div>';
_dialog.name = 'toast';
return _dialog;
};
Dialog.createWarnin = function(){
var _dialog = new Dialog();
_dialog.element = '<div>warnin</div>';
_dialog.name = 'warnin';
return _dialog;
};
var Factory = {};
Factory.NoticeFactory = function(){
return Dialog.createNotice();
}
Factory.ToastFactory = function(){
return Dialog.createToast();
}
Factory.WarninFactory = function(){
return Dialog.createWarnin();
}
var notice = Factory.NoticeFactory();
var toast = Factory.ToastFactory();
var warnin = Factory.WarninFactory();
notice.show(); //notice is show -> <div>notice</div>
toast.show(); //toast is show -> <div>toast</div>
warnin.show(); //warnin is show -> <div>warnin</div>
var Dialog = function(){
this.element = '';
this.name = '';
this.show = function(){
console.log(this.name + ' is show -> ' + this.element);
};
}
Dialog.createNotice = function(){ return '<div>notice</div>'; };
Dialog.createToast = function(){ return '<div>toast</div>'; };
Dialog.createWarnin = function(){ return '<div>warnin</div>'; };
Dialog.factory = function(arg){
var _dialog = new Dialog();
_dialog.element = Dialog[arg]();
_dialog.name = arg;
return _dialog;
};
var notice = Dialog.factory('createNotice');
var toast = Dialog.factory('createToast');
var warnin = Dialog.factory('createWarnin');
notice.show(); //createNotice is show -> <div>notice</div>
warnin.show(); //createWarnin is show -> <div>warnin</div>
toast.show(); //createToast is show -> <div>toast</div>
推荐
从ES6重新认识JavaScript的设计模式-工厂模式
案例分析:图片素材读取
案例分析:log日志模块封装👍
案例分析:宠物工厂封装👍👍👍
案例分析:点披萨工厂封装