Cast
As far as I know (may have a solution I don’t know on TS) has no good solution, so it works:
abstract class Pessoa {
id?: number;
nome?: string;
}
class PessoaFisica extends Pessoa {
public cpf? : string;
public rg? : string;
public sexo? : string;
public profissao? : string;
public data_nascimento? : Date
}
class PessoaJuridica extends Pessoa {
public razaoSocial? : string;
public cnpj? : string;
public ie? : string;
public im? : string;
}
class Cliente {
id? : number;
pessoable? : Pessoa;
}
let c : Cliente = new Cliente();
c.pessoable = new PessoaFisica();
let x = c.pessoable as PessoaFisica;
x.cpf = "123";
Just doing the cast the code has access to the field of that type, because it has been declared that pessoable
(what an ugly name) is like Pessoa
and this guy doesn’t have a field called cpf
. Even if a concrete type is placed that exists for the compiler it is only worth the type specified in the declaration. Doing the cast the guy happens to be PessoaFisica
and then you have access to the field you want. And I didn’t see a way to do everything in one step.
If you make the proper abstractions it may not be so ugly because you would hide this "gambiarra" within the class.
Two camps
A solution that could be: have two fields one for each type and then use only one of them according to type (in the end I go deeper into this idea). This has another problem of abstraction leak, but this whole class is filled with leaks of abstraction, so everything is voidable.
If the abstraction is right is not your problem, class consumer, knowing you have to do this, you will have a way to access this data in a natural way.
Generic type
A little better solution would be to parameterize the type because after all either the client is a natural or legal person, it makes no sense to accept the two in separate fields or together.
abstract class Pessoa {
id?: number;
nome?: string;
}
class PessoaFisica extends Pessoa {
public cpf? : string;
public rg? : string;
public sexo? : string;
public profissao? : string;
public data_nascimento? : Date
}
class PessoaJuridica extends Pessoa {
public razaoSocial? : string;
public cnpj? : string;
public ie? : string;
public im? : string;
}
class Cliente<T> {
id? : number;
pessoa?: T;
}
let c : Cliente<PessoaFisica> = new Cliente();
c.pessoa = new PessoaFisica();
c.pessoa.cpf = "123";
Behold working in the repl it.. And in the TS Playground. Also put on the Github for future reference.
Having to create the person’s object in hand like this is a huge leak of abstraction. One of the things that would need to change is to have builder of the object.
Two types of customer
Another possibility is to create a class that is ClienteFisica
and another kind ClienteJuridica
, but I don’t think it’s any better than the generic version, unless each one’s operations are very different. To make the right decision you need to conceptualize everything right. Not knowing where you want to go, anywhere will do. This can bring several problems in other points, but I don’t want to rule anything out.
Accepting the dynamicity of the die
One more possibility is to use the type any
ali. This way everything would be accessible, but is programming in Javascript and not Typescript. Why adopt a language if you’re going to use resources to circumvent it? You have a case to use, but it’s almost always abuse. Doing this would all be accessible, even what you can’t access.
Union type
Another way that I see possible that is a little better but suffers from the problem of any
, to a lesser extent. You can use a Union type, something like that:
pessoa? : PessoaFisica | PessoaJuridica;
The problem is that if you create a natural person and try to access a CNPJ the code will accept, but in the execution it will break. It’s a solution, but it forces you to be careful all the time.
Heirless
An alternative would be not to make the inheritance. I’m not saying it’s good always, but could have only the Pessoa
and not differentiate whether it is physical or legal. Since you have so many annulable members, you put the members of both on the same object and only fill in a part of the members depending on what type of person you are, which should have a field for that. Of course you will not be able to access all fields always because some will not be filled, is equal to Union type, but here it will consume more space, despite simplifying for having only one type. It can make sense because in many cases the person’s type is just a detail. If you make good abstractions you won’t have to deal with it in consumption. And an alternative is to have auxiliary objects just to keep data that vary by type, so you don’t need to have null fields.
Other issues
Why it has class that preferred to clarify the public visibility and others preferred to leave the default?
Why does a person have nome
? Do they all have it? Okay, pose to be, but why PessoaJuridica
has razaoSocial
? Is it not the same as the name? If it is not, it would need to justify.
Developing software isn’t just coding, it’s thinking correctly about the problem. Don’t think I listed all the problems in that code.
You may think what this has to do with the focus of the question. When conceptualizing right you may not even have this problem.
It’s cool that you set up inheritance and composition the right way, few people do it. Or almost. It’s not that it’s wrong, but it can reverse the issue. If the main one is the person and the client is considered a secondary data for the person, this can get better. And then it would make sense to have several camps papers because the person can actually have various roles. I’m not saying anything, it depends on what you need.
Everything is an alternative. I don’t know which is better for you.
pessoable
is the typePessoa
which has only two propertiesid
andnome
. To access the propertycpf
of your objectPessoaFisica
you have to do the type casting(c as PessoaFisica).cpf ="000000000";
or else(<PessoaFisica> c).cpf ="000000000";
– Augusto Vasques