plain objects VS class instances for model objects

冷暖自知 提交于 2021-02-08 15:20:14

问题


What is the best practice for creating model objects in Angular / TypeScript:

  1. Should I use type annotation with object notation (objects are plain instances of Object)? E.g. let m: MyModel = { name: 'foo' }

  2. Should I use the new operator (objects are instances of the respective prototype)?

  3. Should these two approaches be mixed up and used situationally? (E.g plain objects, when receiving a response from the HttpClient, but new MyModel('foobar') for convenience to create instances by passing properties as constructor arguments)

Background of this question

I'm new to TypeScript and like many other developers I come from popular object oriented languages like Java.

A core concept I understood is that type annotations in TypeScript don't play a role at runtime. They are important for the compiler and for your editor's auto completion. Basically it's ECMAScript with the addition of compile time type checks.

At the time I didn't know this and expected TypeScript to be some kind of "client side Java", I found this Angular bug report: https://github.com/angular/angular/issues/20770

People (who don't understand TypeScript's type concept) complain about the HttpClient not converting the returned plain object to their model class type. Other people are defending this behavior and point out that there are no runtime types in JavaScript.

And here arises my problem: In fact there ARE runtime types in JavaScript. You can create instances of prototypes with the new operator and even check their constructor with the instanceof operator.

In TypeScript you have two ways for creating an instance:

1) Use the object notation (as they show in the Angular tutorial):

hero: Hero = {
  id: 1,
  name: 'Windstorm'
};

2) Use the new-operator:

hero: Hero = new Hero();

At the moment I'm working on a project where these two options are mixed up. I.e. model objects of the same type are instances of Object in some cases and instances of Hero in other cases.

I expect this leading to problems later, because the constructor is only called in the latter case.

My idea for a rule / best practice was to define all model instances as plain objects and to use the constructor for services, components etc. that are created by dependency injection. As a result I wouldn't use the new operator at all.

However I'm not sure if this is a good idea and I couldn't find a best practice recommendation about it.

EDIT

Important note for close voters: I'm not looking for your personal opionion here. I'm rather looking for some kind of officially documented Angular best practice, as I think it's a core design decision that has to be made right from the project start and these approaches should not be mixed up randomly without a specific reason. Maybe the answer is just a simple "There is no official recommendation which decision to make".


回答1:


In my opinion, you should use the new operator to create new objects of your class if you need to perform complex operations on the object itself.

If in case you just need to access the properties only, you may use the object literal to create a new object.

This has got advantages like encapsulation, inheritance, etc. which a developer coming from the Java background could better relate to.

If you directly assign an object like below, then you have to explicitly set functions in it, for example, getName function.

hero: Hero = {
  id: 1,
  name: 'Windstorm',
  getName: function(){ // returns name }
};

However, by defining a class Hero with a function getName and then creating an instance of that class, you would automatically get that function, no matter how many times you create instances.

For a better understanding of Object Oriented Javascript, you may follow the link below:-

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS


Regarding the below issue,

"People (who don't understand TypeScript's type concept) complain about the HttpClient not converting the returned plain object to their model class type."

HttpClient simply returns an object which the server sends, it is on JS to convert it into the required form. You can always map the response into the required model instance by using its constructor which could be defined as below:-

constructor(hero){
  this.id = hero.id;
  this.name = hero.name;
}

and you may map the array returned from the server like below:-

map(hero => new Hero(hero))



回答2:


In fact there ARE runtime types in JavaScript.

Kind of. A value can have a certain type, however variables and properties don't have types bound to them. That said if you type network requests etc.

  fetch("someurl").then(res => res.json()) as Promise<IUser>

and that network request suddenly returns something else than a User, nothing will fail as Typescript types do not exist at runtime. However this behaviour can be added easily with typeguards:

  function isUser(user: any) : user is IUser {
     return Number.isInteger(user.id) && typeof user.name === "string";
  }

which allows you to do typechecks at runtime:

 const result = await fetch("someurl");
 if(!isUser(result)) throw new Error();
 // result is definetly a IUser here

Should I use inheritance or not?

That is a question of preference. I once wrote an application that works with the db heavily, so I had to do a lot of serialization / deserialization, and it really annoyed me to always wrap the response from the db into a class instance. So for my db models I started using the following pattern:

  type User = {
   name: string;
   id: number;
 };

 const User = {
   get(id: number): User { /*...*/ }
   changeName(user: User, name: string) { /*..*/ }
};

That allowed me to write:

 const admin: User = User.get(123);

And I could simply do typecasts on the db and I both had typesafety and nice APIs.

Wether that is a good pattern for your usecase or not really depends on how much serialization / deserialization you do.



来源:https://stackoverflow.com/questions/51742249/plain-objects-vs-class-instances-for-model-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!