[React Framework] Chap 3 - Dựng bộ khung của framework

Trong bài viết trước chúng ta đã cài đặt một project ReactJS ở mức cơ bản nhất, trong bài viết này ta sẽ dựng bộ lên bộ khung của framework ở mức độ HelloWorld.
Chap 1, chúng ta đã phác thảo được cơ bản cấu trúc của bộ framework như sau:

Trong bài viết này chúng ta sẽ tạo một service có tên là HelloWorldService.ts chứa duy nhất một hàm print() trả về chuỗi HelloWorld, cấu trúc thư mục sẽ như sau:

Extension.ts

Có tác dụng viết lại class nếu class đó có plugin, ở đây chúng ta chỉ cần chạy HelloWorld nên chưa cần cài đặt plugin, phương thức get() trả về object luôn

export interface ExtensionInterface {
    get<T>(type: string, Object: object): T
}

class Extension implements ExtensionInterface {
    /**
     * Get rewrite class if available or itself class
     * if class has plugin method, we call them
     */
    get<T>(type: string, Object: object): T {
        const _Object: any = Object
        return _Object
    }
}

export default new Extension()

Với TypeScript có một kiểu dữ liệu động (Type Aliases), ví dụ:

get<T>(): T
=>
get<string>() // Trả về kiểu string
get<CustomClass>() // Trả về CustomClass

ObjectManager.ts

Trả về một đối tượng singleton: là một mẫu thiết kế phần mềm để hạn chế sự khởi tạo của lớp đối tượng (Wikipedia)

export interface ObjectManagerInterface {
    instances: any
    get<T>(Object: object): T
}

class ObjectManager implements ObjectManagerInterface {
    instances: any = {}

    /**
     * Get singleton class
     *
     * @returns {Class}
     * @param Object
     */
    get<T>(Object: object): T {
        const Class: any = Object

        if (!Class.hasOwnProperty('name')) {
            return new Class()
        }

        const className = Class.name

        if (!this.instances[className]) {
            this.instances[className] = new Class()
        }
        return this.instances[className]
    }
}

export default new ObjectManager()

Đối với các class được sử dụng bởi Factory thông thường sẽ cần chèn thêm tên class ở đầu khi khai báo, nhưng đối với TypeScript, tên class có thể lấy được bằng cách [className].name

Nên với TypeScript sẽ không cần:

static className = 'CategoryService';

Phân tích thêm ở sơ đồ mà ta tìm thấy ở Chap 1, ObjectManager có tác dụng trả về singleton class, mà mọi class đều kế thừa từ AbstractFactory, nên ta có thể “tối ưu” thêm ở chỗ này, đưa ObjectManager lên trên, AbstractFactory sẽ chịu trách nhiệm trả về đối tượng singleton luôn.

AbstractFactory.ts

Là một lớp abstract, có tác dụng trả về singleton class theo từng factory (DataResourceFactory, ResourceModelFactory, ServiceFactory, …)

import Extension from '~/framework/Extension'
import ObjectManager from '~/framework/ObjectManager'

export interface AbstractFactoryInterface {
    get<T>(Object: object): T
    getObject<T>(type: string, ClassObject: object): T
}

abstract class AbstractFactory implements AbstractFactoryInterface {
    /**
     * get target to use
     */
    abstract get<T>(Object: object): T

    /**
     * get target class to use
     */
    getObject<T>(type: string, ClassObject: object): T {
        return ObjectManager.get<T>(Extension.get(type.toLowerCase(), ClassObject))
    }
}

export default AbstractFactory

ResourceModelFactory.ts

Là lớp con của AbstractFactory, trả về đối tượng singleton của resource model

import AbstractFactory, { AbstractFactoryInterface } from './AbstractFactory'
import FactoryConstant from '~/view/constant/FactoryConstant'

export interface ResourceModelFactoryInterface extends AbstractFactoryInterface {}

class ResourceModelFactory extends AbstractFactory implements ResourceModelFactoryInterface {
    /**
     * get target resource model to use
     */
    get<T>(Object: object): T {
        return this.getObject(FactoryConstant.RESOURCE_MODEL, Object)
    }
}

export default new ResourceModelFactory()

DataResourceFactory.ts

Là lớp con của AbstractFactory, trả về đối tượng singleton của data resource

import AbstractFactory, { AbstractFactoryInterface } from './AbstractFactory'
import FactoryConstant from '~/view/constant/FactoryConstant'

export interface DataResourceInterface extends AbstractFactoryInterface {}

class DataResourceFactory extends AbstractFactory implements DataResourceInterface {
    /**
     * get target data resource to use
     */
    get<T>(Object: object): T {
        return this.getObject(FactoryConstant.DATA_RESOURCE, Object)
    }
}

export default new DataResourceFactory()

ServiceFactory.ts

Là lớp con của AbstractFactory, trả về đối tượng singleton của service

import AbstractFactory, { AbstractFactoryInterface } from './AbstractFactory'
import FactoryConstant from '~/view/constant/FactoryConstant'

export interface ServiceInterface extends AbstractFactoryInterface {}

class ServiceFactory extends AbstractFactory implements ServiceInterface {
    /**
     * get target service to use
     */
    get<T>(Object: object): T {
        return this.getObject(FactoryConstant.SERVICE, Object)
    }
}

export default new ServiceFactory()

Singleton.ts

Nơi ứng dụng trả về đối tượng singleton online (RestAPI) hoặc offline (Local Database), trong phạm vi bài viết này không có dữ liệu nên sẽ không khởi tạo các lớp Database

import DataResourceFactory from '~/framework/factory/DataResourceFactory'

const resources: any = {}

export interface SingletonInterface {
    getOnline<T>(name: string): T
    getOffline<T>(name: string): T
    get<T>(prefix: string, name: string): T
}

class Singleton implements SingletonInterface {

    /**
     * get online resource by name
     */
    getOnline<T>(name: string): T {
        return this.get<T>('Omc', name)
    }

    /**
     * get offline resource by name
     */
    getOffline<T>(name: string): T {
        return this.get<T>('IndexDB', name)
    }

    /**
     * get object form name and prefix
     */
    get<T>(prefix: string, name: string): T {
        return DataResourceFactory.get<T>(resources[prefix + name])
    }
}

export default new Singleton()

AbstractResourceModel.ts

Là một abstract class, nơi ứng dụng kiểm tra trạng thái của mạng để ra quyết định sử dụng resource online hay offline và trả về resource cuối cùng.

import Singleton from '~/resource-model/Singleton'
import SyncConstant from '~/view/constant/SyncConstant'
import { AbstractDBInterface } from '~/data/AbstractDBInterface'

const Config = {
    online_mode: true,
}

export interface AbstractResourceModelInterface extends AbstractDBInterface {
    resourceName: string
    dataType: string
    getResource(): AbstractDBInterface
    getResourceOnline(): any
    getResourceOffline(): any
}

export default abstract class AbstractResourceModel<S> implements AbstractResourceModelInterface {
    resourceName = 'HelloWorld'
    dataType = SyncConstant.TYPE_HELLOWORLD

    /**
     * Get resource depend on mode
     */
    getResource(): AbstractDBInterface {
        return Config.online_mode
            ? Singleton.getOnline<AbstractDBInterface>(this.resourceName)
            : Singleton.getOffline<AbstractDBInterface>(this.resourceName)
    }

    /**
     * Get online resource
     */
    getResourceOnline(): AbstractDBInterface {
        return Singleton.getOnline<AbstractDBInterface>(this.resourceName)
    }

    /**
     * Get offline resource
     */
    getResourceOffline(): AbstractDBInterface {
        return Singleton.getOffline<AbstractDBInterface>(this.resourceName)
    }

    getList(): S {
        return this.getResource().getList()
    }
}

Config.online_modeAbstractDBInterface đang được fix cứng giá trị cho đơn giản, trong thực tế sẽ khác

HelloWorldResourceModel.ts

Là lớp con của AbstractResourceModel, chỉ ra chính xác resourceName là HelloWorld

import AbstractResourceModel, { AbstractResourceModelInterface } from '~/resource-model/AbstractResourceModel'
import SyncConstant from '~/view/constant/SyncConstant'
import { HelloWorldInterface } from '~/data/api/HelloWorldInterface'

export interface HelloWorldResourceModelInterface extends AbstractResourceModelInterface {}

export default class HelloWorldResourceModel
    extends AbstractResourceModel<HelloWorldInterface>
    implements HelloWorldResourceModelInterface {
    resourceName = 'HelloWorld'
    dataType = SyncConstant.TYPE_HELLOWORLD
}

CoreService.ts

Service lớp cha, chứa những phương thức cơ bản nhất mà mọi service đều phải có

import ResourceModelFactory from '~/framework/factory/ResourceModelFactory'
import AbstractResourceModel, { AbstractResourceModelInterface } from '~/resource-model/AbstractResourceModel'
import { AbstractDBInterface } from '~/data/AbstractDBInterface'

export interface CoreServiceInterface extends AbstractDBInterface {
    compiledResourceModel?: any
    resourceModel: object
    getResourceModel<T>(resourceModel?: object): T
}

export default class CoreService<S> implements CoreServiceInterface {
    compiledResourceModel?: any
    resourceModel: object = AbstractResourceModel

    /**
     * get target Resource Model
     */
    getResourceModel<T>(resourceModel?: object): T {
        if (!resourceModel) {
            if (!this.compiledResourceModel) {
                this.compiledResourceModel = ResourceModelFactory.get<T>(this.resourceModel)
            }
            return this.compiledResourceModel
        }
        return ResourceModelFactory.get<T>(resourceModel)
    }

    getList(): S {
        return this.getResourceModel<AbstractResourceModelInterface>().getList()
    }
}

HelloWorldService.ts

Ghép tất cả những gì chúng ta làm được:

import HelloWorldResourceModel from '~/resource-model/chap3/HelloWorldResourceModel'
import CoreService, { CoreServiceInterface } from '~/service/CoreService'
import ServiceFactory from '~/framework/factory/ServiceFactory'
import { HelloWorldInterface } from '~/data/api/HelloWorldInterface'

export interface HelloWorldServiceInterface extends CoreServiceInterface {
    print(): string
}

class HelloWorldService extends CoreService<HelloWorldInterface> implements HelloWorldServiceInterface {
    resourceModel = HelloWorldResourceModel

    print(): string {
        return "HelloWorld";
    }
}

export default ServiceFactory.get<HelloWorldServiceInterface>(HelloWorldService)

App.tsx

Sử dụng Service chúng ta vừa tạo

import React from 'react'
import HelloWorldService from '~/service/chap3/HelloWorldService'

function App() {
    return (
        <div className="App">
            {HelloWorldService.print()}
        </div>
    )
}

export default App

Kết quả:
image

Tham khảo source code: GitHub - phamdungtsx/react-framework at chap-3

5 Likes

Cho bộ này vào devdocs của mình sau này khác có request contribute thì ném cho họ đọc :smiley:

1 Like