SOLID Essentials Missing Points?

I read so many articles about this, but still I have 2 questions.

Question No. 1 - Regarding Inversion of Dependencies :

It states that high-level classes should not be dependent on low-level classes. Both should depend on abstractions. Abstractions should not depend on the details. Details should depend on abstractions.

eg:

public class BirthdayCalculator { private readonly List<Birthday> _birthdays; public BirthdayCalculator() { _birthdays = new List<Birthday>();// <----- here is a dependency } ... 

Fix: put it in ctor.

 public class BirthdayCalculator { private readonly IList<Birthday> _birthdays; public BirthdayCalculator(IList<Birthday> birthdays) { _birthdays = birthdays; } 
  • If it is in ctor, I will have to send it every time I use the class. Therefore, I will have to save this when calling the BirthdayCalculator class. is it ok to do so?

  • I can argue that after the fix, still - IList<Birthday> _birthdays should not be there ( Birthday in IList ) - but it should be like IList<IBirthday> . I'm right?

Question No. 2 - Regarding the replacement of Liskov :

derived classes must be substituted for base classes

or more precisely:

Let q (x) be a property provable with respect to objects x of type T. Then q (y) must be true for objects y of type S, where S is a subtype of T.

(already read this )

example:

 public abstract class Account { public abstract void Deposit(double amount); } 

I have a class:

 public class CheckingAccount : Account { public override void Deposit(double amount) { ... _currentBalance += amount; } } 

and the bank wants to open a Mortgage Account - like this:

 public class MortgageAccount : Account { public override void Deposit(double amount) { _currentBalance -= amount; //<-----notice the minus } } 

The problem arises when there is a function that accepts mortage as Account and makes a deposit.

 public class Bank { public void ReceiveMoney(Account account, double amount) { double oldBalance = account.CurrentBalance; account.Deposit(amount); //oopssss????? } } 

therefore, here he violates the LSP.

But I do not understand.

Each overridden method will execute different code when overriding, so it will never be replaced by 100%!

the definition did NOT say that "the logic should continue as in the base class (always add (add) positive numbers)"

Example:

What if the CheckingAccount class and the MortgageAccount class share positive numbers, but MortgageAccount also log to db? still interrupting LSP? What is the LSP gap / non-brake boundary?

the definition should determine whatis that border. and he says nothing about it.

What am I missing?

+6
source share
6 answers

LSP says that any promises a base class does should also create subclasses. In the case of the Account, yes, this means that the Deposit deduction from the Mortgage balance is pretty bad. One of the workarounds for this would be if you thought that the balance is the difference between the amount the client owes you and how much you owe it. (I am almost 54% sure that the initial value of โ€œbalanceโ€ is anyway.) A positive balance can mean that the client has money, while a negative balance can mean that he owes money. If you cannot resolve this so that you can process the two accounts in the same way, then they should not be connected - or at least the Deposit method should not be defined in the base class.

As for DIP, this does not really mean that it is not intended to be applied to every line of code. In the end, you must have a specific class somewhere. The bottom line is to limit the dependence of specific classes on sections of code that you must know about them, and minimize the number and size of these sections. This means using it as a universal interface, as you can get as far as you can. For example, if you donโ€™t need all the guarantees that List makes (listing order, duplicates, zeros, etc.), you can declare _birthdays instead of _birthdays instead of IEnumerable . If a constructor is the only thing that knows that your IEnumerable is actually a list, then you more or less adhere to the principle.

(Be careful, however: this does not mean that you can adhere to DIP by simply declaring everything as Object and downcasting as necessary. Downcasting itself can be considered a violation of DIP because you no longer rely on the interface you provided, you are trying to get more specific option.)

+5
source

If it is in ctor, I will have to send it every time I use the class. Therefore, I will have to save this when calling the BirthdayCalculator class. is it ok to do so?

I would like to get a list of birthdays once, make one BirthdayCalculator and pass this. You will only need to create a new BirthdayCalculator if your birthday list changes or if it somehow maintains the state.

I can argue that after the fix, still - IList<Birthday> _birthdays shouldn't be there (Birthday in IList) - but it should be like IList<IBirthday> . I'm right?

It depends. Is birthday just a data object? Does it have any logic? If this is just a bunch of properties / fields, then the interface is full.

If the birthday has logic, I would go with IEnumerable<IBirthday> . List<Birthday> not assigned to IList<IBirthday> , so you may have problems. If you don't need to manipulate it, stick with IEnumerable .


Regarding your second question, the only thing I came across is your naming convention. You do not make deposits in Mortage. I think that if you renamed this method, it could make more sense.

In addition, the account and the account are two completely different animals. One of them is a deposit account, and the other is a loan. I would be very careful trying to use them interchangeably.

Edit

Others have noted that you can make _currentBalance in MortageAccount negative value, and then add to it. If itโ€™s easier for you to understand, then go. However, the internal functions of how a MortageAccount processes its balance are not important if GetBalance or any method that you use to check / display processes it correctly.

+2
source

# 1

# 2

Your logic is corrupted here. When you invest in an account, you add money to it. The mortgage account does not have money in the account (positive balance), and you do not withdraw money from it by making a deposit. The mortgage account has a negative balance, and depositing to the account lowers the balance (adding a negative balance).

+1
source

Regarding the LSP: As I see it, the LSP is mainly associated with the (public) hierarchy contract. You have an account (object), you can credit / debit to it (contract). For a mortgage account, Credit will lead to deduction, and debit will lead to an additional. To verify your account, it's the other way around.

The important thing is that, given the account, you can always ask it to perform credit / debit actions and that all subclasses of the account object will support these operations.

0
source

Dependency Inversion:

If it is in ctor, I will have to send it every time I use the class. Therefore, I will have to save this when calling the BirthdayCalculator class. is it ok to do so?

You need this not when using an object, but when constructing a BirthdayCalendar object. Moving IList<Birthday> to the build interface ensures that all BirthdayCalendar dependencies match the caller's dependencies (i.e. Not new) and what we are trying to achieve.

I can argue that after the fix, still - IList<Birthday> _birthdays should not be there ( Birthday in IList ) - but it should be like IList<IBirthday> . I'm right?

This is not directly related to the principle of dependency inversion. You have disabled the dependency on the caller, and now you are asking about the details of this dependency. It might be a good idea to use IBirthday to access the internal elements of your list instead of Birthday , but this is a question about your interfaces not related to dependency inversion.

Each overridden method will execute different code when overriding, so it will never be replaced by 100%! the definition did NOT say that "the logic should continue, as in the base class (always add (add) positive numbers)"

Example: What if the CheckingAccount class and MortgageAccount wrote positive numbers, but the MortgageAccount also written in db? still breaking LSP ? What is the limit for LSP breaks / non-brakes? the definition should determine whatis what borders. and he says nothing about it.

The Liskov replacement principle is applicable to the ratio of a base class and a derived class . In your example, 2 classes that inherit from a common base are considered, so your ratio of derived class1 and derived class2 . This is the exact opposite of what LSP is talking about, precisely for the reasons you talked about - the logic is different in both derived classes. LSP is the measure of equity of inheritance that you have determined. If you can positively answer the question, can my derived class object be used transparently wherever my base class is used ?, you do not violate the LSP. Otherwise, you violate it.

0
source

As for the first point, you should use a specific low-level class as a private detail of the implementation of another class; the code may be larger than SOLID , if we can say IList<Birthday> _birthdays = new List<Birthday>; but performance is likely to be slightly better with List<Birthday> _birthdays = new List<Birthday> . If you want to use IBirthday rather than Birthday , this is also good if you use IBirthday every time you tend to use Birthday , except when you call constructors or static methods. Note that you need to be consistent in using Birthday vs IBirthday and List<T> vs IList<T> .

Please note that private members should not adhere to the same rules as public members. If the type were to accept a parameter of type List<T> , which would force external code to use this type. The transition from List<T> to IList<T> is likely to require many changes to many different assemblies and will make older versions of assemblies incompatible with new ones. In contrast, if a class has a private member of type List<T> that is never shared with external code, the consequences of changing it to use IList<T> will be limited within the class itself and will not affect consumers of this class.

0
source

All Articles