That's a scenario for the Strategy pattern. Anyways, it something like this (C# ahead)
public interface IChargeRent
{
void Chanrge(Tenant tenant);
}
public class HighClassPropertyRentCollector:IChargeRent
{
//we assume this object contains or it gets the tarrifs
public void Charge(Tenant tenant)
{
if (tenant.IsIrresponsible) { //apply penalty
}
else{ //whatever }
}
}
public class MiddleClassPropertyRentCollector:HighClassPropertyRentCollector {}
public class LowClassPropertyRentCollector:IChargeRent
{
public void Charge(Tenant tenant) {
}
}
public interface ICanChargeRent
{
PropertyClassType PropetyLevel {get;}
}
public class Landlord:ICanChargeRent { }
//in another class
public static IChargeRent GetRentCollector(ICanChargeRent ll)
{
//switch on PropertyLevel and instantiate the corresponding rent charger
}
Middle landlords have, for now, the same behaviour as the high class landlords so that's why I inherit from high class landlords, however that could change so its better to have an explicit rent charging strategy for each landlord. Same for low class landlords.
If there is a certain way to determine the quality of the tenant, then each will get injected a service which will provide that funcitonality. And because the rent charging behaviour is encapsulated their own classes, you can change it without needing to change the Landlord.
The reason my solution is different from your proposal is that charging rent is not really part of what defines a Landlord (yes, I know how it sounds) but a use case of a landlord. After all, if the landlord hires a property manager and tells him to charge rent, does the definition of a landlord changes? Not so much.
Even in this scenario 99% of the code I wrote works unchanged. You only need to make the PropertyManager implement ICanChargeRent while removing the interface from the Landlord. And we can come up with an even more maintainable solution (requiring less changes) if we know more details about the domain.