![067080025564c80f1ab0ec03001bb08a.png](https://i-blog.csdnimg.cn/blog_migrate/45847778711cdfda685e8e981cf8c701.jpeg)
前言
面向对象编程离不开对象的序列化与反序列化。LS大法读取与保存是避不开的话题,开篇就从此入手通过一些场景来说明采用面向对象编程的一些可取之处。
代码仓库
第一章github.comDemo
LearnTsOopshengzheng1981.github.io场景设定及需求目标
此处怕用户权限为老生常谈,故用工作相关的设施设备管理作为需求及应用场景,望能举一反三。
本文的需求目标:
- 简单梳理设施及设备的对象实体及关联关系(ER)
- 简单抽象,并建立类
- 类到JSON文件的序列化与反序列化
- 借助反射机制的序列化与反序列化
实体与关系(ER)
实体两个:设施(Facility)、设备(Device);
关系:一包含关系:一个Facility包含多个Device;
配置:设施类别(Facility Category)。
由于简单,不上ER图了。
抽象(OMG)
抽象基于现实需求以及实际情况,再结合一定的扩展性,设计为如下:
- 建立抽象基类(Element),目的:与万物之母Object建立中间保护层,便于后期可以用instanceof区分自建类及其它对象类,另抽象部分虚拟函数;
- 建立实体基类(EntityElement)与配置基类(ConfigElement),都继承自Element,此目的可区分实体与配置的不同行为,比如后期实体序列化到数据库,配置序列化到JSON;
- 实体基类(EntityElement),抽象实体共有属性,例如ID、名称、描述等;
- 配置基类(ConfigElement),抽象配置共有属性,例如配置名称、配置值等;
- 设施(Facility)、设备(Device)继承实体基类(EntityElement),设施类别(Facility Category)继承配置基类(ConfigElement)。
缺图,后补
序列化与反序列化
为了便于演示,实体基类也暂序列化到本地Indexed DB,目前设计完全支持序列化到Mongo。
要支持自动序列化,必须要借助反射机制(Reflect),TS的反射类库,官方推荐reflect-metadata,类库API很简单,但要真正了解,需要通过学以致用开始,下面就是一简单示例:
import "reflect-metadata";
export const SerializeMetaKey = "Serialize";
//序列化装饰器
export function Serialize(name?: string) {
return (target: Object, property: string): void => {
Reflect.defineMetadata(SerializeMetaKey, name || property, target, property);
};
}
代码似乎什么都没干,就只定义了一条元数据。其实,这就够了,一般的装饰器,就是用来起到标识作用。这个装饰器的应用如下:
//实体基类
export class EntityElement extends Element{
@Serialize()
public _id: string = "";
@Serialize()
public name: string = "";
@Serialize("desc")
public description: string = "";
public count: number = 0;
constructor() {
super();
}
}
上述类,标识id、name、description,可被序列与反序列化,count非序列化字段;_id、name序列化后保存的字段名为原名,description则序列化为desc。
自动序列化逻辑如下:
//序列化
toJSON(): any {
const obj = {};
Object.keys(this).forEach( property => {
const serialize = Reflect.getMetadata(SerializeMetaKey, this, property);
if (serialize) {
if (this[property] instanceof Element) {
obj[serialize] = this[property].toJSON();
} else {
obj[serialize] = this[property];
}
}
});
return obj;
}
//反序列化
fromJSON(obj) {
obj && Object.keys(this).forEach( property => {
const serialize = Reflect.getMetadata(SerializeMetaKey, this, property);
if (serialize) {
if (this[property] instanceof Element) {
this[property].fromJSON(obj[serialize]);
} else {
this[property] = obj[serialize];
}
}
});
}
以序列化(toJSON)为例进行简单说明:
遍历可枚举的自身属性,发现被标识为序列化字段,接着判断该字段是否是自定义类,如是则进行递归序列化。
完整代码可参考:https://github.com/shengzheng1981/learn-ts-oop/blob/master/src/app/chapter1/element/element.ts
代码释疑
Chapter1 Module为方便展示,主要目标:将配置数据类-设施类别-FacilityCategory初始化从IndexDB加载,及序列化到IndexedDB保存。
一.IndexedDB的初始化及加载
let request = window.indexedDB.open(Chapter1Component.DB_Name, Chapter1Component.DB_Version);
request.onerror = (event) => {
//console.log('Database failed to open');
};
//加载IndexedDB
request.onsuccess = (event) => {
//console.log('Database opened successfully');
// Store the opened database object in the db variable. This is used a lot below
this.DB = request.result;
const objectStore = this.DB.transaction(Chapter1Component.DB_Table_Name).objectStore(Chapter1Component.DB_Table_Name);
const req = objectStore.getAll();
req.onsuccess = (event) => {
const data = (event.target as any).result;
//加载IndexedDB存储的原有记录
this.categories = [];
Array.isArray(data) && data.forEach(item => {
const category = new FacilityCategory();
category.fromJSON(item);
this.categories.push(category);
});
this.categories.length > 0 && (this.current = this.categories[0]);
}
};
//IndexedDB初始化及升级
request.onupgradeneeded = (event) => {
// Grab a reference to the opened database
const db = (event.target as any).result;
// Create an objectStore to store our notes in (basically like a single table)
// including a auto-incrementing key
const objectStore = db.createObjectStore(Chapter1Component.DB_Table_Name, { keyPath: "_id" });
objectStore.transaction.oncomplete = (event) => {
//初始化默认数据,并将数据保存到新创建的对象仓库
const objectStore = db.transaction(Chapter1Component.DB_Table_Name, "readwrite").objectStore(Chapter1Component.DB_Table_Name);
Chapter1Component.DB_Default_Data.forEach( (item) => {
const category = new FacilityCategory();
category.fromJSON(item);
category.create();
objectStore.add(category.toJSON());
});
};
};
注意:
1.DB_Version默认不匹配会触发,onupgradeneeded升级事件,在初始化过程中,为演示,默认加载几条Demo数据;
2.升级事件后,会触发onsuccess,加载过程中,调用fromJSON来完成反序列化;
二.配置要素FacilityCategory的增删改
add(){
this.current = new FacilityCategory();
this.current.create();
this.categories.push(this.current);
this.categories = [...this.categories];
const objectStore = this.DB.transaction(Chapter1Component.DB_Table_Name, "readwrite").objectStore(Chapter1Component.DB_Table_Name);
objectStore.add(this.current.toJSON());
this.message.create("success", "添加成功!");
}
remove(item) {
this.modal.confirm({
nzTitle: '提示',
nzContent: '是否确认删除?',
nzOkText: '确定',
nzCancelText: '取消',
nzOnOk: () => {
const objectStore = this.DB.transaction(Chapter1Component.DB_Table_Name, "readwrite").objectStore(Chapter1Component.DB_Table_Name);
const request = objectStore.delete(item._id);
request.onerror = (event) => {
// 错误处理
this.message.create("warning", "删除失败!");
};
request.onsuccess = (event) => {
const index = this.categories.findIndex(category => category._id === item._id);
this.categories.splice(index, 1);
this.categories = [...this.categories];
if (item._id === this.current._id) this.current = null;
this.message.create("success", "删除成功!");
};
}
});
}
save(){
const objectStore = this.DB.transaction(Chapter1Component.DB_Table_Name, "readwrite").objectStore(Chapter1Component.DB_Table_Name);
const request = objectStore.get(this.current._id);
request.onerror = (event) => {
// 错误处理
this.message.create("warning", "保存失败!");
};
request.onsuccess = (event) => {
// 把更新过的对象放回数据库
const requestUpdate = objectStore.put(this.current.toJSON());
requestUpdate.onerror = (event) => {
// 错误处理
this.message.create("warning", "保存失败!");
};
requestUpdate.onsuccess = (event) => {
// 完成,数据已更新!
this.message.create("success", "保存成功!");
};
};
}
注意:
1.在添加及保存的过程中,注意调用toJSON进行序列化;
2.列表中要素的添加和删除按道理在Angular是支持双向绑定,由于用了NG-Zorro的组件,需要this.categories = [...this.categories]来触发检查机制;(其实可以用其它组件或修改该组件来完成双向绑定,此处暂时不表)
三.key的生成
其实较为简单,此处提是为了后期章节中避免重复再提,同时,该key的生成与Mongo生成objectId是兼容的,意味着可以在前端生成_id(前提是了解Mongo的主键机制)。
create() {
const timestamp = (new Date().getTime() / 1000 | 0).toString(16);
this._id = timestamp + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, function() {
return (Math.random() * 16 | 0).toString(16);
}).toLowerCase();
}
最后,本章请关注Chapter1相关代码。
https://github.com/shengzheng1981/learn-ts-oop/tree/master/src/app/chapter1
后期内容
1.如何处理多个类导致Component逻辑臃肿,将逻辑转移到类操作是面向对象及MVC编程的主要目标?
2.如何将Component中实例对象编辑,根据属性字段类型不同,进行界面自动化处理?
3.如何处理类之间关联关系?