Search code examples
c#ooplibrary-design

A couple of questions regarding on OO and library design


Ok. I have some questions regarding to some aspects of OO, and library design.

  1. Should a library be self-sufficient? Eg., can it use an external Dependency Injection framework, or should it implement it own, in a more lightweight manner?

  2. How does the Liskov's Substitution Principle fits on polymorphism, where you don't know the behavior of a method or class? You just expect it to work like it should?

  3. On the source code part, is a bad habit keep the interfaces on a separated folder (eg., /interfaces) from their implementation?

  4. It's also a bad habit delimit generic types (where T : type) on interfaces rather than just in their implementations? (this one I don't think so, but it's just to confirm it).

  5. Is an interface favorable over a abstract class when the object relationship is both, "can do" and "is a", and there's no need of a default implementation for methods and etc?

That's it. Thanks for your time =)


Solution

  • 1. Should a library be self-sufficient?

    Libraries can surely use, for example, Dependency Injection frameworks. However, it is debatable whether you want to force the DI framework upon the user of the library.

    2. How does the Liskov's Substitution Principle fit on polymorphism?

    Liskov's Substitution Principle is about being able to interchange one implementation with another (substitution), and regardless of the behavior, there should be no errors when the user adheres to the contract. For example, swapping a CanonPrinter for an EpsonPrinter should still allow you to print if you use just the methods from the Printer class. Calling something Printer but having a particular implementation (Canon, Epson) underlying it is polymorphism.

    3. Is it a bad habit to keep the interfaces in a folder separate from their implementation?

    It is a personal preference whether you'll want to keep interfaces apart from their implementation. I don't do it; I don't even have an interface for each implementation. I think it only makes sense to have an interface for an implementation if it adds value to your project.

    4. Is it also a bad habit to limit generic types on interfaces rather than just in their implementations?

    If you think a generic type should be limited to a particular type, then you should do that where it makes sense (so, this may be on the interface). For example, having a IPrintableDocument<TPrinter> and not limiting TPrinter to Printer objects would not make sense, so I'd do it.

    5. Is an interface favorable over an abstract class when the object relationship is both "can do" and "is a"?

    Indeed, most people use abstract classes for is a and interfaces for can do relations. The reason: a class can inherit from only one base class but from multiple interfaces. In essence, this means that a class can be only one thing (a Employee is a Person) but can do multiple (it might ICopyDocuments, IWalkAbout, IMakeCoffee). Whatever you do when an interface is both depends on your preferences.


    Most of your questions have to do with the contract of a class (or interface): the contract specifies what a user (another class, someone else's code) can do and cannot do with the class and its members, and it specifies what the user may expect the class will do and will not do.

    For example: the user can only pass objects as arguments to a method when they match the parameter type. The class will only return objects as return values that match the return type. A user can only call members defined in the class, and the class will make sure those methods behave according to the contract (i.e. not throwing errors, no side effects, never returning null).

    Generic type constraints are also part of the contract. I even consider the documentation part of the contract: when it states that a method will never throw an exception, then no implementation should ever throw an exception. There is no syntax or compiler rule enforcing it, but it is part of the contract. Some parts of the contract are enforced by the compiler or the runtime, others are not.

    When a class or interface has a particular contract, then any subclass or implementation can be substituted for it and it will still work when that subclass or implementation adheres to the contract. If it adheres to the contract, then that's Liskov's Substitution Principle (LSP). And it is easily to deviate from it, and many programmers do. Sometimes you have no choice.

    A clear example of a violation of the LSP in the .NET Framework is the ReadOnlyCollection<T> class. It implements the IList<T> interface but has no actual implementation for many of its methods. So if you pass a user expecting a IList<T> a ReadOnlyCollection<T> and the user tries to call list.Add, then an exception will be thrown. So, you cannot always substitute IList<T> for ReadOnlyCollection<T>, so it violates the LSP.