2

According to David West, Object Thinking involves identifying Objects in the problem/business domain that mirror real-world objects; the interface would then reflect the behaviors of that object.

What would happen if we had an object class with alot of different behaviors. How would that not violate the Single Responsibilty Principle?

For instance, take an OrderBatch class with the following interface:

public class OrderBatch{

    public Customer getCustomer(){...};

    public void addOrder(){...}

    public void removeOrder(Order order){...}

    public List<Order> getOrders(){...}

    public void queueForPicking(){...}

    public void markComplete(){...}

    public void printPicklist(){...}

    public void emailPicklist(){...}        

}

These methods all seem to be behaviors you would expect from an OrderBatch.

Now imagine the above interface together with all the implementation code needed to make this work, and you have a Super-Duper God Class.

Is this a violation of the SRP? Why should it be, if these behaviors belong to that object? And if it is a violation, how would I split it?

3
  • The answer depends on the actual design. It would be necessary evaluate the interface within the context it lives at.
    – Laiv
    Commented May 30, 2017 at 17:42
  • 1
    8 methods does not make a God Class. In this example, you are taking SRP too far. That said, it might make sense under SRP to split off the Order stuff from the Pick stuff.
    – user949300
    Commented May 30, 2017 at 23:34
  • @user949300 I'll be very honest, this class is just a sample of a real class I'm working with, with many more methods and about 1300 lines of code. Most of the code is from the implementation of the interface methods. Commented Jun 2, 2017 at 16:33

5 Answers 5

0

There is a difference between a thing and things related to that thing. To illustrate, consider your example:

public class OrderBatch{
    public Customer getCustomer(){...};
    public void addOrder(){...}
    public void removeOrder(Order order){...}
    public List<Order> getOrders(){...}
    public void queueForPicking(){...}
    public void markComplete(){...}
    public void printPicklist(){...}
    public void emailPicklist(){...}        
}

Why wouldn't this be more like this?

public class OrderBatch{
    public int CustomerID;
    public int BatchID;
    public void removeOrder(Order order){...}
    public void markComplete(){...}
}

public class CustomerRepository {
    public Customer getCustomer(int customerID);
}

public class Order {
    public void AddToBatch(int batchID)
}  

public class OrderRepository {
    public Order[] getOrdersByBatchID(int batchID)
}

public class OrderQueue {
    public void Add(OrderBatch batch)
}

public class PickList {
    public PickList(OrderBatch batch);
    public void email(string recipient);
    public void print();
}

Just because something is related to a class doesn't mean its implementation belongs in that class. Your OrderBatch might have the single responsibility of storing all the data for a batch of orders (and maintaining its integrity). Other classes can do stuff with with order batch.

6
  • 1
    I don't like the idea of replacing the Customer or the Batch by their respective ID's. That's giving out an implementation detail to a unrelated class about how Customer and Batch works.
    – T. Sar
    Commented Jun 2, 2017 at 19:12
  • Completely understandable. I used to feel the same way, but experience has shown me that is a bit idealistic-- those IDs are hard to get rid of, e.g. when passing information in a querystring or in JSON, where it is a problem to always include an entire object. Also, if OrderBatch to knows how to retrieve objects that aren't OrderBatches, it seems like a violation of SRP. You're welcome to try to implement your stuff without using the IDs and see where that takes you.
    – John Wu
    Commented Jun 2, 2017 at 19:34
  • 1
    I mostly use my stuff without IDs. My persistance/repository layer accepts objects that implement a specific interface, and deal with them accordingly. The underlying ID and other database related stuff stays firmly closed inside their objects. If I need to translate those to JSON or something else, I just write a new repository that uses JSON strings instead of a SQL database. If I need to write a querystring, I pass the object to someone who knows how to write the query and access the relevant service. Anyways, if you really, really need the IDs...
    – T. Sar
    Commented Jun 2, 2017 at 19:47
  • You can just expose them from inside the customer object, if you keep a reference for one of those.
    – T. Sar
    Commented Jun 2, 2017 at 19:50
  • So you either have a non-domain class that knows a domain entity's ID, or you have a domain class that knows how to format stuff for HTTP transport. The latter seems like a worse violation of SRP, IMO.
    – John Wu
    Commented Jun 2, 2017 at 23:52
4

An order batch is a real thing. Its responsibility is keeping a bunch of order items together. That makes it a fairly complex entity that requires a lot of management tasks. So be it. As long as they make sense in the order batch context, that is just fine.

What is typically named a God class is a class that has hetrogenious behavior and/or data, not so much one that just happens to have a great number of methods and/or properties.

[edit]

Looking at your example though, some things do come to mind. It does not look right to have Email... and Print... methods in there. First, picklist is a class in its own right that may have a constructor that takes a collection of items which may be obtained from the order batch. Then, printing or emailing it is a separate concern. You may need a PickListFormatter class that prepares picklists for presentation purposes.

OrderBatch may be a bad name, perhaps it really is a ProductionBatch. Ask what the purpose of the batch class is (what do the orders have in common?) and have that expressed in the name. This will define the responsibility. Is it a ShippingBatch? OrderBatch says nothing more than "bunch of orders" leaving you guessing for what purpose, making it impossible to judge if a particular method belongs or not. First you need to define the responsibility, once that is clear, picking the right behavior is not so hard any more.

3

"Identifying Objects in the problem/business domain" is a requirements analysis task, which should not be confused with detailed, technical class design. It is one way to use classes as a rough ordering tool for "user stories" or requirements, but they should not be the final classes one wants to create during design and implementation (at least not if one prefers to get some maintainable piece of software).

The classes implemented in real code, however, should follow the SRP. They will be more fine-grained than the classes one uses during requirements analysis and typically on a different level of abstraction.

2
  • 1
    That's a good catch. Objects identified on the business domain just hint of what classes you may end up using, but they are by no means final and absolute. Very good answer.
    – T. Sar
    Commented Jun 2, 2017 at 19:15
  • Right. This is the best answer: you don't try to restrict classes in your application to real-world analogues, because often there are no appropriate analogues at the level of detail you need to work in. Therefore, any book that suggests every class in your application should have some real associated domain concept is a book that should be ignored.
    – Jules
    Commented Jul 10, 2017 at 17:40
2

What would happen if we had an object class with alot of different behaviors. How would that not violate the Single Responsibilty Principle?

Most likely.

Let's speak more in terms of the domain itself. While I'm not an expert at order batching and pick list ordering:

An Order Batch is a collection of orders, not necessarily from the same customer, but in the same phase of fulfillment.

An order batch may go thru a number of different transitions. The order batch may be initially unordered; later a pick list ordering is established based on some fulfillment criteria.

And if it is a violation, how would I split it?

You might consider these separate abstractions: (1) the unordered batch of orders, (2) the ordered pick list based on (3) fulfillment criteria that provides a grouping of line items across orders.

The fulfillment criteria is more likely related to the structuring of the warehouse; a different warehouse might provide a different fulfillment criteria, which would have the effect of ordering the pick list differently. Thus, the fulfillment criteria is a separate responsibility.

Emailing and printing are probably out of scope for any of these. Each merely need to be traversable/navigable so that you can enumerate the underlying elements by other components that handle email and printing concerns.

Creating the order batch is also an independent responsibility, as we can imagine that the choosing the orders that are actually batched together can yield an efficiency that is either more or less optimal.

Is this a violation of the SRP?

If we were to attempt all of these abstractions in a single class that would indeed be a violation of SRP.

However, SRP doesn't mean separate everything into tiny abstractions that then have to be stitched together by the consumer or are awkward and unnatural to use. We have to balance SRP with quality abstraction. SRP tends to argue for separation, whereas quality abstraction tends to argue for bundling. Sometimes, if we can't find a balance between separation and bundling, it is because we have more independent concepts that need to be teased out into their own abstraction.

In this particular case, we need separate concepts for an order batch itself vs. the strategies for batching orders together, and for ordering pick lists, which are related more to their environment. The order batch may go thru states and transitions, which itself is sufficient responsibility (and may use several classes) without also realizing the ordering/grouping strategies.

0

I always map the responsibility of modules to the layer of abstraction that it is part of.https://dzone.com/articles/single-responsibility. A class and its methods both have responsibilities, but they operate at different levels.

I think it is important to maintain both "low representational gap" as well as SRP. I would accomplish this by delegation/composition/inheritance. i.e. the interface of OrderBatch is maintained, but the implementation is delegated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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