使用 Enum 填充字段的通用工厂

Posted

技术标签:

【中文标题】使用 Enum 填充字段的通用工厂【英文标题】:Generic Factory using Enum that populates fields 【发布时间】:2020-08-01 04:21:13 【问题描述】:

我不知道如何让这个工厂 createLetterMap 为其实例生成默认值...

' How to loop over enums '并不是真正的问题,因为我理解这是不可能的,因为类型在运行时不可用,但也许我错过了关于字符串 emums 的一些东西?


enum Letter
    A = "A",
    B = "B",
    E = "E",
    I = "I",


type Vowl = Letter.A | Letter.E | Letter.I

class LMAP
    test()

function createLetterMap<T extends Letter>()
    let lmapExtended = new LMAP() as LMAP & 
        [key in T]?:string
    ;

    // ?? create default values for keys somehow ??

    return lmapExtended;


let vowlMap = createLetterMap<Vowl>()

vowlMap[Letter.E] = "Eat" // OK
// vowlMap[Letter.B] = "Bat" // Error! Good!

let defaultVal = vowlMap[Letter.A]; // undefined. Would like it to be populated.

更一般地说,我想使用字符串枚举的联合来生成键控对象,我可以使用枚举作为键来处理类似这样的情况:

fn(v:Vowl)

  ...

  letterMap[v].someVowlReleatedWork()

  ...


我已经探索了替代方案,只使用了~工作~的实际地图,但如果我能正确指定类型,似乎有一种方法可以让事情变得更清晰......

我想出的最好的方法是制作一个包含在联合类型中的额外枚举数组,并同时使用联合和工厂数组;像下面这样的东西,看起来有点愚蠢:

...

let Vowls = [Letter.A,... ]
createLetterMap<Vowl>(Vowls)

【问题讨论】:

【参考方案1】:

您不能将类型定义用作值,因为它们会在运行时被删除 - for(let key of keyof Vowl 不起作用。

您可以将映射实现为一个类,或者如果您真的想在运行时提取键,您可以使用自定义转换器,例如 ts-transformer-keys。


enum Letter 
  A = "A",
  B = "B",
  E = "E",
  I = "I",


type Vowl = Letter.A | Letter.E | Letter.I

interface IMapper<T>  operation(value: T): void 

class VowlMapper implements IMapper<Vowl> 
  operation(vowel: Vowl) 
    console.log("VowlMapper: " + vowel);
  


class LetterMap<T extends Letter> 

  private map: IMapper<T> &  [key in T]?: string ;

  constructor(map: IMapper<T>) 
    this.map = map;
  

  get(letter: T) 
    return this.map[letter] ?? letter;
  

  set(letter: T, value: any) 
    this.map[letter] = value;
  

  operation(value: T) 
    return this.map.operation(value);
  



let vowlMap = new LetterMap(new VowlMapper())

vowlMap.set(Letter.E, "Eat");
// vowlMap.set(Letter.B, "Bat"); // Error! Good!

console.log(vowlMap.get(Letter.A))  // A
//console.log(vowlMap.get(Letter.B))  // error
console.log(vowlMap.get(Letter.E))  // Eat

vowlMap.operation(Letter.A); // VowlMapper: A 

查看playground example。

【讨论】:

是的,仅仅隐藏实际的地图可能是更好的,或者至少更安全的方式......虽然我无法找到一种方式来说明“实例A 有键 K [B,C,D]" 并且可以关闭并写入 A[B] 而无需创建多个间接和组合级别......不过感谢您的方法!【参考方案2】:

我发现的“干净”方法(至少在使用输出时)使用 const 枚举数组。解决方案有点……厚;而且我认为用输入枚举集合覆盖 LMAP 字段存在一些严重的风险......

虽然在分配默认值时,我似乎需要强制转换为任何值,但我不确定为什么。鉴于输入数组必须是一组字母枚举,我并不担心每个人说的演员阵容,但这有点奇怪......

enum Letter
    A = "A",
    B = "B",
    E = "E",
    I = "I"    


// By using a const array for subsets we can have an iterable structure and something to derive type from
const Vowls = <const> [
    Letter.A,
    Letter.E,
    Letter.I
];

// Whatever class we'll 'extend'
class LMAP;
// Accepts a const array of the original enum type, uses it for type info and iteration 
function createLMAP<T extends Readonly<Array<Letter>>,T2>(letterArray:T,defaultVal:T2)    
    let n = new LMAP() as LMAP & [key in typeof letterArray[number]]:T2;
    letterArray.forEach(key=>                
        n[key as T[number]] = defaultVal as any; // Not sure why the need to cast to any here...
    );
    return n;


// voila
let vm = createLMAP(Vowls,0);

// tests
Vowls.forEach(v=>
    vm[v] += 1;
)
vm[Letter.A] = 1;
vm[Letter.B] = 1; // Error thanks to no implicit any

// derive a union type for use elsewhere...
type Vowl = typeof Vowls[number]; 
// e.g.
function work(v:Vowl)
    vm[v] = 1;

work(Letter.A)
work(Letter.B) // Err !

我不接受这个作为答案,但我个人会走这条路。

【讨论】:

以上是关于使用 Enum 填充字段的通用工厂的主要内容,如果未能解决你的问题,请参考以下文章

如何将通用 Tryparse 与 Enum 一起使用?

Mybatis plus通用字段自动填充的最佳实践总结

在 Django 中自动填充一组通用的多对多字段?

在线问题反馈模块实战:实现对通用字段内容自动填充功能

在线问题反馈模块实战:实现对通用字段内容自动填充功能

DTO 中返回 Enum 类的通用写法