159

At a recent job interview, I couldn't answer a question about SOLID -- beyond providing the basic meaning of the various principles. It really bugs me. I have done a couple of days worth of digging around and have yet to come up with a satisfactory summary.

The interview question was:

If you were to look at a .Net project that I told you strictly followed SOLID principles, what would you expect to see in terms of the project and code structure?

I floundered around a bit, didn't really answer the question, and then bombed out.

How could I have better handled this question?

3

6 Answers 6

197

S = Single Responsibility Principle

So I'd expect to see a well organised folder/file structure & Object Hierarchy. Each class/piece of functionality should be named that its functionality is very obvious, and it should only contain logic to perform that task.

If you saw huge manager classes with thousand of lines of code, that would be a sign that single responsibility wasn't being followed.

O = Open/closed Principle

This is basically the idea that new functionality should be added through new classes that have a minimum of impact on/require modification of existing functionality.

I'd expect to see lots of use of object inheritance, sub-typing, interfaces and abstract classes to separate out the design of a piece of functionality from the actual implementation, allowing others to come along and implement other versions along side without affecting the original.

L = Liskov substitution principle

This has to do with the ability to treat sub-types as their parent type. This comes out of the box in C# if you are implementing a proper inherited object hierarchy.

I'd expect to see code treating common objects as their base type and calling methods on the base/abstract classes rather than instantiating and working on the sub-types themselves.

I = Interface Segregation Principle

This is similar to SRP. Basically, you define smaller subsets of functionality as interfaces and work with those to keep your system decoupled (e.g. a FileManager might have the single responsibilty of dealing with File I/O, but that could implement a IFileReader and IFileWriter which contained the specific method definitions for the reading and writing of files).

D = Dependency Inversion Principle.

Again this relates to keeping a system decoupled. Perhaps you'd be on the lookout for the use of a .NET Dependency Injection library, being used in the solution such as Unity or Ninject or a ServiceLocator system such as AutoFacServiceLocator.

12
  • 37
    I've seen plenty of LSP violations in C#, everytime someone decides their particular subtype is specialized and therefore doesn't need to implement a piece of the interface and just throws an exception on that piece instead... This is a common junior approach to solving the problem of misunderstanding interface implementation and design Commented Jun 24, 2013 at 15:48
  • 2
    @JimmyHoffa That is one of the main reasons I insist on using Code Contracts; going through the thought process of designing the contracts helps a lot of getting people out of that bad habit.
    – Andy
    Commented Jun 24, 2013 at 16:02
  • 13
    I don't like the "LSP comes out of the box in C#" and equating DIP to Dependency injection practice.
    – Euphoric
    Commented Jun 24, 2013 at 17:18
  • 3
    +1 but Dependency Inversion <> Dependency Injection. They play well together, but dependency inversion is much more than just dependency injection. Reference: DIP in the wild Commented Jun 24, 2013 at 18:42
  • 3
    @Andy: what helps as well is unit tests defined on the interfaces against which all implementers (any class that can/is instantiated) are tested. Commented Jun 24, 2013 at 18:43
18

A lot of small classes and interfaces with dependency injection all over the place. Probably in a big project you would also use a IoC framework to help you construct and manage the lifetimes of all those small objects. See https://stackoverflow.com/questions/21288/which-net-dependency-injection-frameworks-are-worth-looking-into

Note that a big .NET project that STRICTLY follows SOLID principles does not necessarily mean a good codebase to work with to everybody. Depending on who the interviewer was, he/she may have wanted you to show that you understand what SOLID means and/or check how dogmatically you follow design principles.

You see, to be SOLID, you need to follow:

S ingle responsibility principle, so you will have many small classes each of them doing one thing only

O pen-closed principle, which in .NET is usually implemented with dependency injection, which also requires the I and the D below...

L iskov substitution principle is probably inmpossible to explain in c# with a one-liner. Luckily there are other questions addressing it, e.g. https://stackoverflow.com/questions/4428725/can-you-explain-liskov-substitution-principle-with-a-good-c-sharp-example

I nterface Segregation Principle works in tandem with the Open-Closed principle. If followed literally it would mean preferring a big number of very small interfaces rather that few "big" interfaces

D ependency inversion principle high-level classes should not depend on low-level classes, both should depend on abstractions.

1
  • SRP doesn't mean "do one thing only." Commented Apr 3, 2018 at 22:33
13

Some basic things I would expect to see in the codebase of a shop that espoused SOLID in their daily work:

  • Many small code files - with one class per file as best practice in .NET, and the Single Responsibility Principle encouraging small modular class structures, I'd expect to see a lot of files each containing one small, focused class.

  • Lots of Adapter and Composite patterns - I'd expect the use of a lot of Adapter patterns (a class implementing one interface by "passing through" to the functionality of a different interface) to streamline plugging in a dependency developed for one purpose into slightly different places where its functionality is also needed. Updates as simple as replacing a console logger with a file logger will violate LSP/ISP/DIP if the interface is updated to expose a means to specify the filename to use; instead, the file logger class will expose the additional members, and then an Adapter will make the file logger look like a console logger by hiding the new stuff, so only the object snapping all this together has to know the difference.

    Similarly, when a class needs to add a dependency of a similar interface as an existing one, to avoid changing the object (OCP), the usual answer is to implement a Composite/Strategy pattern (a class implementing the dependency interface and consuming multiple other implementations of that interface, with varying amounts of logic allowing the class to pass a call through to one, some, or all of the implementations).

  • Lots of interfaces and ABCs - DIP necessarily requires that abstractions exist, and ISP encourages these to be narrowly-scoped. Therefore, interfaces and abstract base classes are the rule, and you'll need a lot of them to cover the shared dependency functionality of your codebase. While strict SOLID would necessitate injecting everything, it's obvious you have to create somewhere, and so if a GUI form is only ever created as a child of one parent form by performing some action on said parent, I have no qualms newing up the child form from code directly within the parent. I just typically make that code its own method, so if two actions of the same form ever opened the window, I just call the method.

  • Many projects - The point of all of this is to limit the scope of change. Change includes needing to recompile (a relatively trivial exercise anymore, but still important in many processor- and bandwidth-critical operations, like deploying updates to a mobile environment). If one file in a project has to be rebuilt, all files do. That means if you place interfaces in the same libraries as their implementations, you're missing the point; you'll have to recompile all usages if you change an implementation of the interface because you'll also be recompiling the interface definition itself, requiring usages to point to a new location in the resulting binary. Therefore, keeping interfaces separate from usages and implementations, while additionally segregating them by general area of use, is a typical best practice.

  • Lots of attention paid to "Gang of Four" terminology - The design patterns identified in the 1994 book Design Patterns emphasize the bite-size, modular code design that SOLID seeks to create. The Dependency Inversion Principle and Open/Closed Principle, for instance, are at the heart of most of the identified patterns in that book. As such, I'd expect a shop that adhered strongly to SOLID principles to also embrace the terminology in the Gang of Four's book, and name classes according to their function along those lines, such as "AbcFactory", "XyzRepository", "DefToXyzAdapter", "A1Command" etc.

  • A generic Repository - In keeping with ISP, DIP and SRP as commonly understood, Repository is nearly ubiquitous in SOLID design, as it allows consuming code to ask for data classes in an abstracted way without needing specific knowledge of the retrieval/persistence mechanism, and it puts the code that does this in one place as opposed to the DAO pattern (in which if you had, for instance, an Invoice data class, you'd also have an InvoiceDAO that produced hydrated objects of that type, and so on for all data objects/tables in the codebase/schema).

  • An IoC Container - I hesitate to add this one, as I actually do not use an IoC framework to do the majority of my dependency injection. It quickly becomes a God Object anti-pattern of throwing everything into the container, shaking it up and pouring out the vertically-hydrated dependency you need through an injected factory method. Sounds great, until you realize that structure gets pretty monolithic, and the project with the registration info, if "fluent", now has to know everything about everything in your solution. That is a lot of reasons to change. If it isn't fluent (late-bound registrations using config files), then a key piece of your program relies on "magic strings", a whole different can o' worms.

2
  • 1
    why the downvotes?
    – KeithS
    Commented Jun 26, 2013 at 15:25
  • I think this is a good answer. Instead of being similar to the many blog posts on what these terms are, you have listed examples and explanations that go to show their usage and value
    – Crowie
    Commented Jun 29, 2013 at 16:14
11

Distract them with Jon Skeet's discussion of how the 'O' in SOLID is "unhelpful and poorly understood" and get them talking about Alistair Cockburn's "protected variation" and Josh Bloch's "design for inheritance, or prohibit it".

Short summary of Skeet's article (though I wouldn't recommend dropping his name without reading the original blog post!):

  • Most people don't know what 'open' and 'closed' in 'open-closed principle' mean, even if they think they do.
  • Common interpretations include:
    • that modules should always be extended through implementation inheritance, or
    • that the source code of the original module can never be changed.
  • The underlying intention of OCP, and Bertrand Meyer's original formulation of it, is fine:
    • that modules should have well-defined interfaces (not necessarily in the technical sense of 'interface') that their clients can depend on, but
    • it should be possible to expand what they can do without breaking those interfaces.
  • But the words "open" and "closed" just confuse the issue, even if they do make for a nice pronounceable acronym.

The OP asked, "How could I have better handled this question?" As a senior engineer conducting an interview, I would be immeasurably more interested in a candidate who can talk intelligently about the pros and cons of different code design styles than someone who can rattle off a list of bullet points.

Another good answer would be, "Well, that depends on how well they've understood it. If all they know is the SOLID buzzwords, I'd expect abuse of inheritance, overuse of dependency injection frameworks, a million small interfaces none of which reflect the domain vocabulary used to communicate with product management...."

0
6

There's probably a number of ways that this can be answered with varying amounts of time. However, I think this is more along the lines of "Do you know what SOLID means?" So answering this question probably just boils down to hitting the points and explaining it in terms of a project.

So, you expect to see the following:

  • Classes have a single Responsibility (e.g. a data access class for customers will only get customer data from the customer database).
  • Classes are easily extended without effecting existing behavior. I don't have to modify properties or other methods in order to add additional functionality.
  • Derived classes can be substituted for base classes and functions that use those base classes do not have to unwrap the base class to the more specific type in order to handle them.
  • Interfaces are small and easy to understand. If a class makes use of an interface, it does not need to depend on several methods to accomplish a task.
  • Code is abstracted enough such that high-level implementation is not concretely dependent on specific low-level implementation. I should be able to switch the low-level implementation without effecting the high-level code. For example, I can switch my SQL data access layer for a Web-service based one without effecting the rest of my application.
4

This is an excellent question, although I think it is a tough interview question.

SOLID principles really govern classes and interfaces and how they related to each other.

This question is really one that has more to do with files and not necessarily classes.

A brief observation or answer I would give is that generally you will see files that hold just an interface, and often the convention is that they start with a capital-I. Beyond that, I would mention that the files would not have duplicated code (especially within a module, application, or library), and that code would be shared carefully across certain boundaries between modules, applications, or libraries.

Robert Martin discusses this topic in the realm of C++ in Designing Objected Oriented C++ Applications Using the Booch Method (see the sections on Cohesion, Closure, and Reusability) and in Clean Code.

1
  • .NET coders IME typically follow a "1 class per file" rule, and also mirror folder/namespace structures; the Visual Studio IDE encourages both practices, and various plugins like ReSharper can enforce them. So, I would expect to see a project/file structure mirroring the class/interface structure.
    – KeithS
    Commented Jun 25, 2013 at 23:48

Not the answer you're looking for? Browse other questions tagged or ask your own question.