How to model classes that can be extendable?
In the field of software development, extensibility specifically refers to inheritance, and inheritance is not the correct approach here.
The above class object is used to do the required job. But this model class serves another purpose and that is to pass information which is not received as a part of JSON response.
"Another purpose" is an SRP violation. Different purposes should be represented individually, specifically so that a change to one does not inherently cause a change in the other.
The explanation in your question is a bit ambiguous, but if I understand you correctly, your application is a man in the middle, i.e. the second Person
class you show is the data you send back to your end user, and the first Person
class is the data you receive from an external web service?
If that is the case, then you should always separate these two DTOs, as they represent the exchange between different sets of layers (i.e. external service => you, and you => end user).
Which means that your code should contain several bits. The DTO from the external web service:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
Your own DTO which you return to your end user:
public class PersonModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public bool IsNewPerson { get; set; }
}
Note that I'm using a different name for clarity's sake here. You can choose your own name or even reuse the same one (if they're in different namespaces).
And then you need some mapping logic. This mapping logic is the exact spot where you can inject your additional IsNewPerson
value. Note I'm just inventing some example logic here to showcase how you could cleanly structure this.
public PersonModel GetPersonById(int id)
{
var person = _externalService.GetPerson(id);
var isNewPerson = _magic8Ball.TellMeTrueOrFalse();
return MapToPersonModel(person, isNewPerson);
}
private PersonModel MapToPersonModel(Person p, bool isNewPerson)
{
return new PersonModel()
{
FirstName = p.FirstName,
LastName = p.LastName,
Address = p.Address,
IsNewPerson = isNewPerson,
};
}
Obviously, replace _magic8Ball.TellMeTrueOrFalse()
with your custom logic.
If I misunderstood, and both purposes you talk about are both interactions between your application and the end user, there are a few more options you have.
You could still separate the classes like you did above, and then each endpoint simply uses their own class to achieve their result, without conflict.
Or you could promote reusability for the person-related field using composition (over inheritance). For example:
// Purpose 1
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
// Purpose 2
public class CreatedPerson
{
public Person Person { get; set; }
public bool IsNewPerson { get; set; }
}
Again, the CreatedPerson
name is a generic example. A better name depends on what this "purpose 2" is, and you can choose a more apt name.
A third option is to consider that you might have this same IsNewXXX
type of field for many different entities, not just person, so you could consider an even more resuable generic class:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
public class CreatedObject<T>
{
public T Value { get; set; }
public bool IsNew { get; set; }
}
This CreatedObject<T>
makes it so that you don't have to create this same kind of class for every entity you expose. For example, if you expose Person
, Animal
and Car
, now you don't have to write custom CreatedPerson
, CreatedAnimal
and CreatedCar
classes, you can simply use CreatedObject<T>
with the appropriate generic type (CreatedObject<Person>
, CreatedObject<Animal>
, CreatedObject<Car>
)
Which of these approaches is the most appropriate heavily relies on context which your question is somewhat light on.
But the main takeaway here is don't use the same class for more than one purpose.