How detailed should a object be? - c#

I'm working on a kind of a HMI application and is creating objects to define a specific machine. Lets say this is a car for the sake of argument.
A object for the engine is obvious. There are a few common sensors on the engine, and this is a few objects mounting to a few properties on the engine object. The throttle property is a input of course.
The car has atleast one door. Each door can have a window, it can be openable and it may be electrically operated. If it's electrically operated it will depend on power from the car to operate.
Now, should I expose the door as a property on the car object, or would it be most sensible to keep it private and having the car object operate the door in OpenDoor and RollDownWindow functions? What about events? Should I expose events on the engine, example LowOnOil event, or should I deal with it in the car object witch in turn could have an event like EngineIsLowOnOil?
How would you do this?

This, and all similar questions, can be answered by considering why you are building the model. There is absolutely no point in creating a model in isolation from the problem you are trying to solve, and in general it is not possible to do so.
For example, if you are building an electronic fuel injection control, system, the number of doors on the car (and posibly even the car itself) is of no interest, and should not be modelled.

Let us explore a little your example of opening the door of a car (say the front left). One could take several approaches (including those which you suggest):
Car.OpenFrontLeftDoor
Car.OpenDoor(FRONTLEFT)
Car.Door(FRONTLEFT).Open
Car.Part(DOOR_FRONTLEFT).Open
Car.Part(DOOR_FRONTLEFT).DoAction(OPEN)
None of them is right or wrong, it depends on the situation. I am sure there are many more ways too.
Number 1 is very much a hard-coded function approach. This would be good for very simple, fixed situations. But it would become unmanageable if your model needs to accomodate variation.
Number 5 is taking a parameterised approach. The latter is a lot more flexible and requires a greater design nous to pull off but could be overkill for a simple problem.
Also bear in mind that your car object can present an external interface different to the internal implemenation. For example, you could use approach 5 internally but present an interface such as in 1, and translate the function calls under the hood (no pun intended).
Ultimately the ability to make decisions like this comes from experience. Expose yourself to good OO design, read books, examine the source code of good software. And above all, try different designs out and see for yourself what works and why.

Einstein said "Make it as simple as possible, but no simpler."
Start with an empty object. As you develop the entire model, add to the object only those attributes that are NECESSARY.

I think this is a very generic question that is hard to answer. My best answer would be "It depends on your needs and the problem domain you are trying to solve".

In the scenario you have given I usually tend to think about it in a real life situation.
So the door is not private to the car i.e. the door of the car is publically accessible. The car does not open the door (unless it is a pretty cool car!) a User would open the door. Hence the door should probably be a public property of the car.
In terms of exposing the events, it really depends on whether you intend on handing them. For example, the OnLowOil event is probably an event you would want to be handled i.e. notify the user who would then perhaps do Car.Engine.FillOil

If in doubt, consider the problem from a different approach: "How would I test these objects?" "How would I test the window?" "What would make testing easiest?" Being a lazy programmer, that's a very important question for me to answer. I want writing the tests to be dirt simple, yet highly effective in proving that my code works.
In your case, you have a car with doors that can open and close, and the doors have windows that can open and close, and they may or may not be electrically operated. How would you test the "window-down" function? Is the window's existence dependent upon the engine? Is it even dependent on the car, or is it really dependent only on the door? Do different doors have different shaped windows with different rules or different amounts of travel? Do different doors use identical electric motors to drive the windows, or are the window motors different for left-mounting versus right mounting?
And consider that a customer may give you a requirement that says "the windows only work when the engine is running", but is that really true? In most cars the windows work when the car's power switch is turned to "on". Put another way, is the window operation dependent on a spinning crankshaft in an engine, or is it actually dependent on 12 volts of electricity? So do you really need an engine instance in a car to test a window, or do you just need a battery?
Once you start asking those questions, you're likely to conclude that a power window is deserving of its own set of tests (up/down/part-way/full-travel/etc.), so it becomes a good candidate for being its own class. The number of different doors might be complex, so you figure a "door factory" might be an appropriate class to create that can manage all the different kinds of doors. Testing things in isolation is always easier: look at the harder questions about what the engine throttle setting should be to test the window-up function? What about the transmission gear, or the door open/close state? All these permutations make it hard to test, even though they have no bearing on whether the window works or not. So all these lead me to think the window classes should be tested standalone as much as possible. In the real world, you'd only need 12 volts to test a power window mounted in a door. So in your modeled world, you'd only need a mock or fake power supply object to provide the modeled power, not an engine object or a transmission gear. It's a simpler test.
After you take those sorts of ideas into consideration, you start realizing that passing dependencies in the tests (here's a power supply for the window test) makes them easy to test; thus the way to assemble the real components in your model would mean encapsulating construction in factories would make for the easiest way to create your car; so building the car might suggest the abstract factory pattern be used to inject all the dependencies into the car.
You might even start with a simple-but-brittle Car class with a hard coded Engine and hard coded Doors, and then refactoring it to add tests like those above. You would likely still end up with independent Car, Door, and Window objects, and an abstract factory pattern to create them all based on the model of car being built. This idea is called "emergent design."

I like to keep things simple. In your Domain/Objects think of how you would speak about the object to determine what way to model it, would you say my car needs oil or my car's engine needs oil?
It depends on the domain. The Domain for a car factory would refer to cars differently than a car rental Domain.
I would have numberous window properties for each window and events off them, and a window state/position as well.

There are no right or wrong models; there are models that are more or less useful for your specific purpose.
As others have said, think first on what information is relevant to your needs. And then discard the rest. That process of removing unnecessary detail is called abstraction. If we didn't perform abstraction, our models would be identical to the real-world entities that they represent! Which would be useless, since the ultimate goal of modelling is obtaining a simplified version of reality that allows us to reason about it in its absence.

I think there are several people here making the same point, but I will try again.
You are asking questions like should I or should I not expose "Door Opened" event. There is no answer to this question other than with another one: how are you going to use it
Ultimately the model is the means not the goal and as such the model cannot be defined outside the context. Even (especially) if you are building your model to sell it as a standalone product you have to think how your customers will use it which - again - is a discussion about context.
Trying answering such questions based one anything else (gut feel?) will be an exercise in counting demons on the end of the needle

Related

Singleton Service classes in c++

Coming from a .NET/C# Background and having solid exposure to PRISM, I really like the idea of having a CompositionContainer to get just this one instance of a class whenever it is needed.
As through the ServiceLocator this instance is also globally accessible this pretty much sums up to the Singleton Pattern.
Now, my current Project is in c++, and I'm at the point of deciding how to manage plugins (external dll loading and stuff like that) for the program.
In C# I'd create a PluginService, export it as shared and channel everything through that one instance (the members would basically only amount to one list, holding the plugins and a bunch of methods). In c++ obviously I don't have a CompositionContainer or a ServiceLocator.
I could probably realize a basic version of this, but whatever I imagine involves using Singletons or Global variables for that matter. The general concern about this seems to be though: DON'T EVER DO GLOBALS AND MUCH LESS SINGLETONS.
what am I to do?
(and what I'm also interested in: is Microsoft here giving us a bad example of how to code, or is this an actual case of where singletons are the right choice?)
There's really no difference between C# and C++ in terms of whether globals and singletons are "good" or "bad".
The solution you outline is equally bad (or good) in both C# and C++.
What you seem to have discovered is simply that different people have different opinions. Some C# developers like to use singletons for something like this. And some C++ programmers feel the same way.
Some C++ programmers think a singleton is a terrible idea, and... some C# programmers feel the same way. :)
Microsoft has given many bad examples of how to code. Never ever accept their sample code as "good practices" just because it says Microsoft on the box. What matters is the code, not the name behind it.
Now, my main beef with singletons is not the global aspect of them.
Like most people, I generally dislike and distrust globals, but I won't say they should never be used. There are situations where it's just more convenient to make something globally accessible. They're not common (and I think most people still overuse globals), but they exist.
But the real problem with singletons is that they enforce an unnecessary and often harmful constraint on your code: they prevent you from creating multiple instances of an object, as though you, when you write the class, know how it's going to be used better than the actual user does.
When you write a class, say, a PluginService as you mentioned in a comment, you certainly have some idea of how you plan it to be used. You probably think "an instance of it should be globally accessible (which is debatable, because many classes should not access the pluginservice, but let's assume that we do want it to be global for now). And you probably think "I can't imagine why I'd want to have two instances".
But the problem is when you take this assumption and actively prevent the creation of two instances.
What if, two months from now, you find a need for creating two PluginServices? If you'd taken the easy route when you wrote the class, and had not built unnecessary constraints into it, then you could also take the easy route now, and simply create two instances.
But if you took the difficult path of writing extra code to prevent multiple instances from being created, then you now again have to take the difficult path: now you have to go back and change your class.
Don't build limitations into your code unless you have a reason: if it makes your job easier, go ahead and do it. And if it prevents harmful misuse of the class, go ahead and do it.
But in the singleton case it does neither of those: you create extra work for yourself, in order to prevent uses that might be perfectly legitimate.
You may be interested in reading this blog post I wrote to answer the question of singletons.
But to answer the specific question of how to handle your specific situation, I would recommend one of two approaches:
the "purist" approach would be to create a ServiceLocator which is not global. Pass it to those who need to locate services. In my experience, you'll probably find that this is much easier than it sounds. You tend to find out that it's not actually needed in as many different places as you thought it'd be. And it gives you a motivation to decouple the code, to minimize dependencies, to ensure that only those who really have a genuine need for the ServiceLocator get access to it. That's healthy.
or there's the pragmatic approach: create a single global instance of the ServiceLocator. Anyone who needs it can use it, and there's never any doubt about how to find it -- it's global, after all. But don't make it a singleton. Let it be possible to create other instances. If you never need to create another instance, then simply don't do it. But this leaves the door open so that if you do end up needing another instance, you can create it.
There are many situations where you end up needing multiple instances of a class that you thought would only ever need one instance. Configuration/settings objects, loggers or wrappers around some piece of hardware are all things people often call out as "this should obviously be a singleton, it makes no sense to have multiple instances", and in each of these cases, they're wrong. There are many cases where you want multiple instances of just such classes.
But the most universally applicable scenario is simply: testing.
You want to ensure that your ServiceLocator works. So you want to test it.
If it's singleton, that's really hard to do. A good test should run in a pristine, isolated environment, unaffected by previous tests. But a singleton lives for the duration of the application, so if you have multiple tests of the ServiceLocator, they'll all run on the same "dirty" instance, so each test might affect the state seen by the next test.
Instead, the tests should each create a new, clean ServiceLocator, so they can control exactly which state it is in. And to do that, you need to be able to create instances of the class.
So don't make it a singleton. :)
There's absolutely nothing wrong with singletons when they're
appropriate. I have my doubts concerning CompositionContainer (but
I'm not sure I understand what it is actually supposed to do), but
ServiceLocator is the sort of thing that will generally be a singleton
in any well designed application. Having two or more ServiceLocator
will result in the program not functionning as it should (because a
service will be registered in one of them, and you'll be looking it up
in another); enforcing this programatically is positive, at least if you
favor robust programming. In addition, in C++, the singleton idiom is
used to control the order of initialization; unless you make
ServiceLocator a singleton, you can't use it in the constructor of any
object with static lifetime.
While there is a small group of very vocal anti-singleton fanatics,
within the larger C++ community, you'll find that the consensus favors
singletons, in certain very restricted cases. They're easily abused
(but then, so are templates, dynamic allocation and polymorphism), but
they do solve one particular problem very nicely, and it would be silly
to forgo them for some arbitrary dogmatic reason when they're the best
solution for the problem.

Domain Driven Design API Question

I'm new to DDD and I am working on my first project, which is for an online golf outing registration process. my requirements are pretty simple. users register for the outing and can optionally add a foursome. they can also sponsor a hole with a message and a few other things, but i want to hash our the foursome stuff first.
so, my first though my aggregate contains the a registration entity, foursome value object (which contains a team name and 4 player value objects).
when designing the api, i'm thinking the following pseudo code:
Registration reg = new Registration();
Foursome foursome = reg.CreateFoursome("My Team");
foursome.Player1.Assign("John Doe", 5, ShirtSize.XL);
reg.Register();
My question is, one of the internal components of the aggregate is being exposed to the client code, so am I opening my self up for issues? any flaws with this simple design or alternative apis?
any help would be great as i am in a state of analysis paralysis right now!
thanks
Lets start with Aggregate definition:
A cluster of associated objects that are treated as a unit for the
purpose of data changes. External references are restricted to one
member of the Aggregate, designated as the root. A set of consistency
rules applies within the Aggregate's boundaries.
Aggregate is a group of objects that you would not want multiple users to edit at the same time because it can break domain invariants. Aggregate is also a life cycle unit. It is hard to answer your question without knowing what these invariants, consistency and life cycle rules are. Would creating two Foursomes on the same Registration be bad? Would Foursome with unassigned Player1 be invalid/inconsistent? Would not calling Register on Registration object will 'corrupt' it? If one of the the answers is true then you should not expose your objects like that. This code should be hidden inside your aggregate.
It also looks like Foursome is not a Value Object because it is mutable. It maybe an entity that should be protected by Registration aggregate root.
// PlayerInfo is a value object
public static Registration CreateNew(String foursomeName, PlayerInfo player1, ...) {
if (foursomeName == null) {
throw new ArgumentNullException("foursomeName");
}
if (player1 == null) {
throw new ArgumentNullException("player1");
}
Registration reg = new Registration();
Foursome foursome = reg.CreateFoursome("My Team");
foursome.Player1.Assign(player1);
if(player2 != null) {
foursome.Player2.Assign(player2);
}
reg.Register();
// return consistent and valid Registration instance
return reg;
}
Again, this can be not what you want, it really depends on your domain model. Maybe your Aggregate root should be an entity like FoursomeRegistartion. Maybe Players are aggregates themselves if they can exist outside Foursome/Registration boundary. As others have said, it is hard to get model right the first time. Have a first implementation and refactor continuously.
For a first shot it isn't bad. It is important when starting design not to get too hung up on the small details. How much time you spend worrying about the robustness of the design is going to be primarily a factor of the project's importance. You will continually learn as you build, and over time, your old code will require refactoring to enable new details.
Take for instance, your FourSome class I assume has four players. Could you instead have a Team class which has a Size constraint, such that a Players collection could be constrained to that number of players? Should there be a Player class (or perhaps that is the type of Player1?)?
The answers to these questions will have implications toward the extensibility of your system.
I encourage you to write each of your scenarios into tests (you could use user stories or use cases too, but developers like to write code), and as you write the tests, fill in the Registration, Player, and Foursome/Team class implementation to make the tests succeed. As you expand your system, your tests will change, and so will your design.
Post Addition 1:
Domain driven design is intended to be an approach to developing the classes and data structures your application will use such that it "models" the problem domain. In your case, you are working with a Golf Outing Registration system. Therefore, as you think about the entities which might make up such a system, you might describe how a Team Captain registers his/her Team (including other Players) by providing his Registration. The registration might be for an Event, which itself may have details such as Organizer, Sponsor, etc. As you see, each of the "capitalized" names become your entities (classes). At least for the first draft of your design. As you discover more information about the relationships between your classes, especially in how they interact (a Player is added to a team), you will flesh out the methods of your class.
During this process you can inadvertently introduce design flaws. For instance, a FourSome is, technically, a type of Team limited to four players. Does it make sense to derive a class from Team and set a limit of four players, or does it make sense to put a constraint on Team? That is a design decision determined by... your business rules and you. Is it a mistake to take one approach over the other? Time will tell, as you will need to constantly refactor in order to expand the system.
You will find many patterns during your design process that may make your design process easier, but a new designer typically does not have the experience to recognize when to use them. If you do, kudos to you. You will not find a perfect design the very first time you are designing. I still look back (15 years now) at designs I thought were awesome, and shake my head at my youthful exuberance.
You should describe your problem (to yourself or on paper). Underline the nouns. They are your candidate classes. Tell a story for each user interaction with the system, and the verbs should get you started toward what the methods are on each class. Avoid using the Registration to do all the work, but separate the responsibility of the classes to where it is appropriate. For instance, you can add a player to a team, or you can have a player add itself to a team. Determining where that responsibility lies should be guided (but not dictated) by the SRP, among other design guidance.
Finally, there is no right answer in design. I could tell you based on my extensive design experience but limited golf experience what I THINK it should be, but ultimately your best design depends on the features, scope, and design goals (like extensibility). Start with your best shot, write tests against it, and your design flaws will emerge before you know it. :D
Post-Revision 2:
I think you are reading too much into Eric Evan's guidance. I don't believe he is saying that you can't expose Player from Foursome.

How can I implement DI/IoC for a repeated and variable process without creating kernels on demand?

I know, this probably wins the award for longest and most confusing question title. Allow me to explain...
I am trying to refactor a long-running batch process which, to be blunt, is extremely ugly right now. Here are a few details on the hard specifications:
The process must run several times (i.e. in the tens of thousands);
Each instance of the process runs on a different "asset", each with its own unique settings;
A single process consists of several sub-processes, and each sub-process requires a different slice of the asset's settings in order to do its job. The groups are not mutually exclusive (i.e. some settings are required by multiple sub-processes).
The entire batch takes a very long time to complete; thus, a single process is quite time-sensitive and performance is actually of far greater concern than design purity.
What happens now, essentially, is that for a given asset/process instance, a "Controller" object reads all of the settings for that asset from the database, stuffs them all into a strongly-typed settings class, and starts each sub-process individually, feeding it whichever settings it needs.
The problems with this are manifold:
There are over 100 separate settings, which makes the settings class a ball of mud;
The controller has way too much responsibility and the potential for bugs is significant;
Some of the sub-processes are taking upwards of 10 constructor arguments.
So I want to move to a design based on dependency injection, by loosely grouping settings into different services and allowing sub-processes to subscribe to whichever services they need via constructor injection. This way I should be able to virtually eliminate the bloated controller and settings classes. I want to be able to write individual components like so:
public class SubProcess : IProcess
{
public SubProcess(IFooSettings fooSettings, IBarSettings barSettings, ...)
{
// ...
}
}
The problem, of course, is that the "settings" are specific to a given asset, so it's not as simple as just registering IFooSettings in the IoC. The injector somehow has to be aware of which IFooSettings it's supposed to use/create.
This seems to leave me with two equally unattractive options:
Write every single method of IFooSettings to take an asset ID, and pass around the asset ID to every single sub-process. This actually increases coupling, because right now the sub-processes don't need to know anything about the asset itself.
Create a new IoC container for each full process instance, passing the asset ID into the constructor of the container itself so it knows which asset to grab settings for. This feels like a major abuse of IoC containers, though, and I'm very worried about performance - I don't want to go and implement this and find out that it turned a 2-hour process into a 10-hour process.
Are there any other ways to achieve the design I'm hoping for? Some design pattern I'm not aware of? Some clever trick I can use to make the container inject the specific settings I need into each component, based on some kind of contextual information, without having to instantiate 50,000 containers?
Or, alternatively, is it actually OK to be instantiating this many containers over an extended period of time? Has anybody done it with positive results?
MAJOR EDIT
SettingsFactory: generates various Settings objects from the database on request.
SubProcessFactory: generates subprocesses on request from the controller.
Controller: iterates over assets, using the SettingsFactory and SubProcessFactory to create and launch the needed subprocesses.
Is this different than what you're doing? Not really from a certain angle, but very much from another. Separating these responsibilities into separate classes is important, as you've acknowledged. A DI container could be used to improve the flexibility of both Factory pieces. The implementation details are, in some ways, less critical than improving the design, because once the design is improved, implementation can vary more readily.

Should I put actors in the Domain-Model/Class-Diagram?

When designing both the domain-model and class-diagrams I am having some trouble understanding what to put in them.
I'll give an example of what I mean:
I am doing a vacations scheduler program, that has an Administrator and End-Users. The Administrator does a couple of things like registering End-Users in the program, changing their previleges, etc. The End-User can choose his vacations days, etc.
I initially defined an Administrator and End-User as concepts in the domain-model, and later as classes in the class-diagram.
In the class-diagram, both classes ended up having a couple of methods like
Administrator.RegisterNewUser();
Administrator.UnregisterUser(int id);
etc.
Only after some time I realised that actually both Administrator and End-User are actors, and maybe I got this design totally wrong. Instead of filling Administrator and End-User classes with methods to do what my Use-Cases request, I could define other classes from the domain to do them, and have controllers handle the Use-Cases(actually, I decided to do one for each Use-Case). I could have a UserDatabase.RegisterNewUser() and UserDatabase.UnregisterUser(int id);, for example, instead of having those methods on the Administrator class.
The idea would be to try to think of the whole vacation-scheduler as a "closed-program" that has a set of features and doesn't bother with things such as authentication, that should be internal/protected, being that the only public things I'd let the outside world see would be its controllers.
Is this the right approach? Or am I getting this totally wrong? Is it generally bad idea to put Actors in the domain-model/class-diagrams? What are good rules of thumb for this?
My lecturer is following Applying UML and Patterns, which I find awful, so I'd like to know where I could look up more info on this described actor-models situation.
I'm still a bit confused about all of this, as this new approach is radically different from anything I've done before.
I would not say that your design, as you speculate, is totally wrong, in fact Administrator and End-User are valid domain objects that should be represented somehow in the Class Diagrams. Just because you identify those two entities as actors doesn't mean they should be excluded from the Domain, remember, your domain is your scope, or "relevant context", that is, the set of useful objects that play a relevant role in your solution.
You may start with basic objects that comprise your model, just write them down, without further analysis, like brain storming...
Vacation
Location
Travel Agent
Schedule
User
Reservation
Reservation Service (This can be an interface for accessing the vacation reservation stuff)
Then try to establish the relationships between those objects, if you can not find any relationship that means you are not choosing the right objects, if they are part of the same problem domain then they must be related somehow.
After some time, you will discover that there are objects missing, try to encapsulate as much as possible, and represent the correct abstractions, Design Patterns might help you out with that.
When every possible object is represented with all its properties and methods, then you should have a fully, although not yet finished, functional design.
I know you should have a lot of theory in your mind, so get going, without fear, I myself have used this approach successfully many times at work.
Finally get a copy of UML Distilled
Regards,
I think you should learn more about domain modeling and process of getting from use cases to class diagrams. Actors as classifiers can be part of class diagrams, however for class diagrams used for analysis and design are modeling the system you develop and an actor is external entity. When you are using use cases and use case diagrams, the goal is to identify functional and non-functional requrements, therefore define the scope of the system to develop and external entities - roles or systems - interacting with the system you are developing. In use case diagrams you can find sometimes a box representing system boundary, which encompasses all use cases, which will be realized in your system, but actors are out of the box. When domain modeling, you usually forget about the system altogether, because you want to capture the way the domain works. Often special diagrams and modeling elements are used for domain modeling. As I said, the point is to understand the domain in which the system will be used. Class diagrams in the analysis and design phase describe the system you are developing, so no actors can be inside.

How to implement SOLID principles into an existing project

I apologize for the subjectiveness of this question, but I am a little stuck and I would appreciate some guidance and advice from anyone who's had to deal with this issue before:
I have (what's become) a very large RESTful API project written in C# 2.0 and some of my classes have become monstrous. My main API class is an example of this -- with several dozen members and methods (probably approaching hundreds). As you can imagine, it's becoming a small nightmare, not only to maintain this code but even just navigating the code has become a chore.
I am reasonably new to the SOLID principles, and I am massive fan of design patterns (but I am still at that stage where I can implement them, but not quite enough to know when to use them - in situations where its not so obvious).
I need to break my classes down in size, but I am at a loss of how best to go about doing it. Can my fellow StackOverflow'ers please suggest ways that they have taken existing code monoliths and cut them down to size?
Single Responsibility Principle - A class should have only one reason to change. If you have a monolithic class, then it probably has more than one reason to change. Simply define your one reason to change, and be as granular as reasonable. I would suggest to start "large". Refactor one third of the code out into another class. Once you have that, then start over with your new class. Going straight from one class to 20 is too daunting.
Open/Closed Principle - A class should be open for extension, but closed for change. Where reasonable, mark your members and methods as virtual or abstract. Each item should be relatively small in nature, and give you some base functionality or definition of behavior. However, if you need to change the functionality later, you will be able to add code, rather than change code to introduce new/different functionality.
Liskov Substitution Principle - A class should be substitutable for its base class. The key here, in my opinion, is do to inheritance correctly. If you have a huge case statement, or two pages of if statements that check the derived type of the object, then your violating this principle and need to rethink your approach.
Interface Segregation Principle - In my mind, this principle closely resembles the Single Responsibility principle. It just applies specifically to a high level (or mature) class/interface. One way to use this principle in a large class is to make your class implement an empty interface. Next, change all of the types that use your class to be the type of the interface. This will break your code. However, it will point out exactly how you are consuming your class. If you have three instances that each use their own subset of methods and properties, then you now know that you need three different interfaces. Each interface represents a collective set of functionality, and one reason to change.
Dependency Inversion Principle - The parent / child allegory made me understand this. Think of a parent class. It defines behavior, but isn't concerned with the dirty details. It's dependable. A child class, however, is all about the details, and can't be depended upon because it changes often. You always want to depend upon the parent, responsible classes, and never the other way around. If you have a parent class depending upon a child class, you'll get unexpected behavior when you change something. In my mind, this is the same mindset of SOA. A service contract defines inputs, outputs, and behavior, with no details.
Of course, my opinions and understandings may be incomplete or wrong. I would suggest learning from people who have mastered these principles, like Uncle Bob. A good starting point for me was his book, Agile Principles, Patterns, and Practices in C#. Another good resource was Uncle Bob on Hanselminutes.
Of course, as Joel and Jeff pointed out, these are principles, not rules. They are to be tools to help guide you, not the law of the land.
EDIT:
I just found these SOLID screencasts which look really interesting. Each one is approximately 10-15 minutes long.
There's a classic book by Martin Fowler - Refactoring: Improving the Design of Existing Code.
There he provides a set of design techniques and example of decisions to make your existing codebase more manageable and maintainable (and that what SOLID principals are all about). Even though there are some standard routines in refactoring it is a very custom process and one solution couldn't be applied to all project.
Unit testing is one of the corner pillars for this process to succeed. You do need to cover your existing codebase with enough code coverage so that you'd be sure you don't break stuff while changing it. Actually using modern unit testing framework with mocking support will lead encourage you to better design.
There are tools like ReSharper (my favorite) and CodeRush to assist with tedious code changes. But those are usually trivial mechanical stuff, making design decisions is much more complex process and there's no so much tool support. Using class diagrams and UML helps. That what I would start from, actually. Try to make sense of what is already there and bring some structure to it. Then from there you can make decisions about decomposition and relations between different components and change your code accordingly.
Hope this helps and happy refactoring!
It will be a time consuming process. You need to read the code and identify parts that do not meet the SOLID principles and refactor into new classes. Using a VS add-in like Resharper (http://www.jetbrains.com) will assist with the refactoring process.
Ideally you will have good coverage of automated unit tests so that you can ensure your changes do not introduce problems with the code.
More Information
In the main API class, you need to identify methods that relate to each other and create a class that more specifically represents what actions the method performs.
e.g.
Let's say I had an Address class with separate variables containing street number, name, etc. This class is responsible for inserting, updating, deleting, etc. If I also needed to format an address a specific way for a postal address, I could have a method called GetFormattedPostalAddress() that returned the formatted address.
Alternatively, I could refactor this method into a class called AddressFormatter that takes an Address in it constructor and has a Get property called PostalAddress that returns the formatted address.
The idea is to separate different responsibilities into separate classes.
What I've done when presented with this type of thing (and I'll readily admit that I haven't used SOLID principles before, but from what little I know of them, they sound good) is to look at the existing codebase from a connectivity point of view. Essentially, by looking at the system, you should be able to find some subset of functionality that is internally highly coupled (many frequent interactions) but externally loosely coupled (few infrequent interactions). Usually, there are a few of these pieces in any large codebase; they are candidates for excision. Essentially, once you've identified your candidates, you have to enumerate the points at which they are externally coupled to the system as a whole. This should give you a good idea of the level of interdependency involved. There usually is a fair bit of interdependency involved. Evaluate the subsets and their connection points for refactoring; frequently (but not always) there ends up being a couple of clear structural refactorings that can increase the decoupling. With an eye on those refactorings, use the existing couplings to define the minimal interface required to allow the subsystem to work with the rest of the system. Look for commonalities in those interfaces (frequently, you find more than you'd expect!). And finally, implement these changes that you've identified.
The process sounds terrible, but in practice, it's actually pretty straightforward. Mind you, this is not a roadmap towards getting to a completely perfectly designed system (for that, you'd need to start from scratch), but it very certainly will decrease the complexity of the system as a whole and increase the code comprehensibility.
OOD - Object Oriented Design
SOLID - class design
Single Responsibility Principle - SRP - introduced by Uncle Bob. Method, class, module are responsible only for doing single thing(one single task)
Open/Closed Principle - OCP - introduced by Bertrand Meyer. Method, class, module are open for extension and closed for modification. Use a power of inheritance, abstraction, polymorphism, extension, wrapper. [Java example], [Swift example]
[Liskov Substitution Principle] - LSP - introduced by Barbara Liskov and Jeannette Wing. A subtype can replace supertype without side effects
Interface Segregation Principle - ISP - introduced by Uncle Bob. Your interface should be as small as possible
[Dependency Inversion Principle(DIP)] - DIP - introduced by Uncle Bob. Internal class, layer should not be depended on external class, layer. For example when you have aggregation[About] dependency you should rather use some abstraction/interfaces. [DIP vs DI vs IoC]
6 principles about packages/modules(.jar, .aar, .framework):
what to put inside a package
The Release Reuse Equivalency
The Common Closure
The Common Reuse
couplings between packages
The Acyclic Dependencies
The Stable Dependencies
The Stable Abstractions
[Protocol Oriented Programming(POP)]

Categories