3

In Object Oriented Programming, since we use classes and methods in these classes, when we are going to implement a certain functionality we must decide where it belongs, in other words, on which class it belongs.

This raises questions like: "should I create a new class and add this there" or "should I add this to one existing class".

The main things I'm aware of are: (i) there is no right way to do it, there are many possible ways that will end up working and (ii) implementing one way and later refactoring is usual.

Actually, when following up the develoment of ASP.NET Core on Github I saw some functionality from time to time move from here to there.

My issue is the starting point: I'm going to implement a new functionality, I know the logic that needs to be coded, I know the business rules, I just don't know where to place it.

I know some theory like SOLID and a little bit of DDD. I like DDD because it gives categories of classes like "entities", "value objects", "repositories", "domain services", so it seems to give a starting point.

My question is how to use this all in practice. Is there some technique that helps deciding where (on which class) a certain logic belongs? What is the way of thinking that I should adopt? My question is really this: how should I think, what is the line of thought I must follow, to make this decision to get started (obviously not make a choice once and for all, as this can change, but at least get started)?

Christophe
  • 74,672
  • 10
  • 115
  • 187
user1620696
  • 4,817
  • 7
  • 31
  • 46
  • Possible duplicate of [What is the real responsibility of a class?](https://softwareengineering.stackexchange.com/questions/220230/what-is-the-real-responsibility-of-a-class) – gnat May 14 '17 at 20:31
  • 1
    Spend some time designing and modeling. Before thinking in code, conceptualize the solution, the components, the responsability of each component. Once you have the global view is easier to figure out where a method/attribute/algorithm should go. The experience will make this process easier, shorter and more intuitive. – Laiv May 14 '17 at 20:58
  • 1
    When in doubt: create a new class. Creating a new class is always the safest approach because you can add this new class as dependency to any other class. In case after some time you have still a single line of code using the new behavior you can still move your functionality to that class. – Timothy Truckle May 14 '17 at 21:15
  • 1
    { When in doubt }: { create a new class }. { Creating a new class } is { always the safest approach } because { you can add this new class } as { dependency to any other class}. { In case after some time } you { have still a single line of code } using { the new behavior } you can { still move your functionality } { to } { that } { class }. – Robert Harvey May 15 '17 at 21:07
  • 3
    When in doubt: do not create a new class. Think about how to minimize the size of the code base, and reduce its fundamental complexity (not the complexity metric). The choice of algorithms, and how you structure your code, generally determines its complexity. Avoid cargo cult design such as SOLID. – Frank Hileman May 15 '17 at 22:28

4 Answers4

2

Well, this is a very broad question, and it's sort of an art, but here are some areas of thought that may help you along, in order from most common/important to uncommon.

Follow design patterns Does the application have an extant design pattern that governs the placement of this functionality? For example, in an MVC site, action methods obviously belong in a controller. In a multi-tiered application, text formatters belong in the presentation tier; data access belongs in a data access layer. If your function creates objects, maybe it belongs in a factory class. Etc.

Minimize scope. What data does the function need? If the function operates on data that is held within an existing object, maybe it belongs inside that object so that you can keep your variables private/tightly scoped.

Minimize call dependencies. Dependencies are the bane of complex software. If you can make it private if you put it in one place but are forced to make it public or static somewhere else, go the private route. Conversely, if you find that you need to change other functions from private to public in order to get your new function to work, it is probably in the wrong place.

Does it make sense to others? If some other developer needs this function, but doesn't know where it is, where is he likely to look first? E.g. if it's a static helper function it might belong in a static class called Helpers. If it operates on a system runtime object, maybe it should be written as an extension method. Make the choice sensible and they will thank you.

Release cycle. If the function is likely to change often, it probably shouldn't go into a core library that is shared across different projects that has a slow versioning lifecycle. Conversely, if it's very stable code, it probably doesn't need to be wrapped in a pluggable architecture.

Licensing or IP. If the function comprises valuable intellectual property, you may want to locate it in a separate library that can be sold separately.

Security. If the functionality is sensitive, you may want to separate it so you can use code access permissions or put it on a different server entirely.

John Wu
  • 26,032
  • 10
  • 63
  • 84
1

This is a fairly broad question, but the overriding principle you should bear in mind is this. You should have classes that individually do relatively little, are very well encapsulated, implement properties properly and are generally designed as simply as possible. This means you may end up with many classes, and this is perfectly ok.

What this means, is that if you have a new piece of functionality and it's not immediately obvious it belongs on an existing class, then it probably doesn't and you should try to think of ways to make that functionality stand-alone and very loosely coupled with the rest of the system. That makes it much easier to refactor later, if you ever need to.

Your tight couplings should be intra-class and your loose couplings should be inter-class. Always work towards this and you won't go far wrong.

Bradley Thomas
  • 5,090
  • 6
  • 17
  • 26
  • 1
    Also, if you have new functionality and it clearly clearly does belong in one particular class, add it there. The SRP is overrated and misunderstood. – user949300 Aug 20 '17 at 04:54
0

Generally speaking, you should try to minimize the total amount of code, maximizing reuse, and maximize encapsulation at the same time. It is difficult to give general answers, and if you use only a set of "principles", you may wind up with solutions that are generic, not specific to your problem. The best solution for each such task, adding a feature, will be specific to the feature.

For example, if I were told to add "undo and redo" to an existing application, if the application was designed from the beginning to support undo and redo, it would not be difficult. But such a thing is usually not planned up front, and adding the feature becomes complex. Undo and redo can be implemented in many ways. One might be a stack of snapshots of the model state. Another might be a stack of individual property changes to the model state. In either case, undo and redo functionality can be centralized, but one solution (snapshots) is less invasive to the application design.

Frank Hileman
  • 3,922
  • 16
  • 18
0

If you cannot decide now, maybe YAGNI. Frank makes a good case for reducing fundamental complexity. If it will fit logically in an existing class at this point, then go ahead with that. Doing this will generally make it easier to troubleshoot when problems come later, rather than having a complex architecture right at the start which may prove harder to implement and debug.

You can always refactor and create new classes later when there are more compelling reasons to do it (e.g. another piece of business logic is required, that looks related to your current one and both of them look like they could belong to a class and share variables), especially if you write the logic in a way that it isn't tightly coupled to the existing class.

William L
  • 104
  • 3