Polymorphism in Typescript

Asked

Viewed 509 times

5

I am creating my application with the following structure: An abstract class Person, and inheriting from Person, the classes PessoaFisica and PessoaJuridica, And finally, a class Cliente receiving Pessoa as an attribute.

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;

}

The problem is that when I urge the class in this way:

let c:Cliente = new Cliente();
c.pessoable = new PessoaFisica();

I can’t access the attribute cpf using c.pessoable.cpf. Where am I going wrong?

  • pessoable is the type Pessoa which has only two properties id and nome. To access the property cpf of your object PessoaFisica you have to do the type casting (c as PessoaFisica).cpf ="000000000"; or else (<PessoaFisica> c).cpf ="000000000";

1 answer

6


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.

  • you have opened up my horizon of ideas and helped me even with other problems in other modeling. Studying a little further by sinking the typescript I found the typeguards tool that could help in this case. let documento:string = (c.pessoa.isPessoaFisica()) ? c.pessoa.cpf : c.pessoa.cnpj

  • public isPessoaFisica() : this is PessoaFisica{&#xA; return 'cpf' in this;&#xA; }

  • 1

    @Marcoslibanori I was wondering if TS had something of the kind and I even thought that it had to have been created by Anders, this seems to be a viable way yes, but it can still have a modeling problem, as I said, depending on the case. Deep down what he’s doing is a cast assured way. I don’t know if it needs to be this complex I think it doesn’t, at least it should be simpler.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.