1

I have a design question about data. I have a class "T" that has a single interface handling two data types as described below. The class T is constructed to handle one of the two data types. Users of T class do not need to know which type of data T class handles.

  • Data class "L" that has a known (limited) number of instances, which are constructed during runtime and they do not change. I plan to use a singleton to hold a container for all such data objects. In the class T, I can directly access the singleton and therefore avoid the need for passing the data object as a function argument.
  • Data class "UL" that have an unknown (unlimited) number of instances. The class T works on each instance one at a time. It seems I will have to pass this type of data in as an argument. This would be fine if T happens to handle UL. But if T handles data type "L", then the argument of "UL" will end up not being used. Is this a legitimate design? It feels strange as the UL argument can be redundant. But can it also be harmless?
  • At this point both type of objects are mutable during their lifetime. The data objects will not change in "T" / "Ts", but could change in class "P".
  • The data type "L" is needed because it has a distinct business meaning and L class have a pre-defined number of instances. the "L" data is like global data, while the "UL" data is like user data. The "UL" data is handled one by one.

I was also wondering if there is a way to avoid passing the "UL" object as a function argument. For example, I can set up a reference outside the class T, and each time the UL object changes, the new UL is assigned to the reference. Would this allow me to hide the different instances of UL behind a fixed reference? Can I then somehow access the fixed reference in class T, in a way similar to the singleton for data type L?

I got stuck in this design and I feel I may have missed something big.


  • A T object may work with either "L" type or "UL" type. Multiple "T" objects are aggregated in another class Ts. The number of T objects within a Ts object can vary.
  • A Ts object is used in a user class "P", which read "UL" objects one by one, and pass the "UL" object to the "Ts" object that it holds. The "Ts" object then pass the UL object to the individual "T" object, which may or may not use the "UL" object.
  • I have this design because it seems desirable to have a single interface for the class "T", so that they can be used in a consistent way within "TS" / "P" object.
class P { Ts ts_; public void work(UL_Data ul_data) { ts_.work(ul_data) } } class Ts { IList<T> ts_; public void work(UL_Data ul_data) { //... foreach(var t in ts_) { double val = t.read(ul_data); //.... } //... } } class T { //set to true if the class read data type L bool data_type_L; public double read(UL_Data ul_data) { if(!data_type_L) //...read data type L from singleton else //read data type UL from function argument ul_data } } 
22
  • Is T part of the public API? Is threading a concern? How does T know when to handle L or not? Can L and UL share an interface?
    – Theraot
    CommentedFeb 4, 2020 at 20:42
  • 4
    It's a little hard to understand this scenario since you've described it so abstractly; it may be helpful if you edited this question to explain a little bit more about what these classes actually are and what the purpose of all this is. Anyway, why does T have just a single interface if it needs to be used in two different ways? And if a T object is constructed which handles data type L, then how does the T object know exactly which L object needs to be used?CommentedFeb 4, 2020 at 21:55
  • 1
    @Fabio Yes I agree T should not care how data class is created. T is constructed using some sort of factory method.
    – DavidY
    CommentedFeb 5, 2020 at 1:06
  • 1
    I'm feeling the need to know the How, When, and Why of data_type_L being set.CommentedFeb 5, 2020 at 2:17
  • 3
    This is too abstract, and due to the abstraction we cannot tell, for example, if these fields are final or mutable during the objects' lifetimes. These are important distinctions if you want to reason over different approaches, such as fuller use of OOP.
    – Erik Eidt
    CommentedFeb 5, 2020 at 15:34

2 Answers 2

2

If understand your question correctly, you have a list that needs to contain two types of objects which perform work. However, the methods for these two types differ in their arguments; one requires a single input (ul_data) while the other can do the work without any inputs at all (to wit, because it obtains the data from somewhere else).

In other words, your desire for a common interface is not driven by methods in common (there aren't any), but is driven by the need to put both classes in a List<T>.

The most basic way to do this is to use a marker interface (for inclusion in the list) but leave the method off. The calling code would then need to filter the list and call each item in a fashion appropriate to its type.

interface IMarkerInterface { //Empty } class TUL : IMarkerInterface { public void Read(ULData ulData) { //Do something with the data } } class TL : IMarkerInterface { public void Read() { //Get the data from somewhere else, and do something with it } } 

Then you can put your objects in the same list:

var list = new List<IMarkerInterface> { new TUL(), new TL() }; 

To iterate over the list and pass arguments, the caller unfortunately needs to know the type. That is the only way for code to pass an argument sometimes but not all the time.

So to iterate over the lists you could do something list this:

foreach (var tl in list.OfType<TL>()) { tl.Read(); } foreach (var tul in list.OfType<TUL>()) { tul.Read(argument); } 

That all being said, this is sort of a last resort. There is probably a way to refactor your object model so that you do not need to check the type of the individual object to know how to call it-- for example, if the ul_data could be passed when the instance is constructed. But to advise you on how to go about this, we'd need to know more about these class' true relationships, i.e. what they are for. Just going by the names T, UL, and L it's impossible to say.

6
  • Thanks @John Wu. I think this would work. In the actual work the TL and TUL may be sorted. If I use SortedDictionary<int, IMarkerInterface>, is there anything similar to list.ofType<TL>()?
    – DavidY
    CommentedFeb 6, 2020 at 2:53
  • myDictionary.Values.OfType<T>()
    – John Wu
    CommentedFeb 6, 2020 at 3:18
  • I also feel I can refactor the coding so that L and UL can have a uniform interface. Do you think as an alternative I can use singletons to hold the data, so that both data type can be used as work(), without any parameters? I know threading is a concern, but I feel I can control it.
    – DavidY
    CommentedFeb 6, 2020 at 20:16
  • If you mean setting a static variable temporarily to hold the data instead of passing it as an argument, that is a terrible design, regardless of threading concerns. I wish you would share more about the actual problem so I could help you find an answer.
    – John Wu
    CommentedFeb 6, 2020 at 21:23
  • Thank you so much, I almost end up using a singleton to replace the function argument. Can you let me know the reason why this is such a bad idea, other than potential issues with threading?
    – DavidY
    CommentedFeb 7, 2020 at 13:57
1

It's OK to pass things that, depending on state, might not be used.

Consider for a moment a multiplication method:

x.multiply(y); 

This method returns x * y where x is some object that was constructed with an int that it now holds. Which means this will pass:

assert(new X(2).multiply(2) == 4); 

So far this all seems reasonable but what happens when it looks like this:

assert(new X(0).multiply(2) == 0); 

Does this require the implementation to contain an if? Is it bad practice to seem like you depend on the 2 when you really don't?

No. It's absolutely fine to do this without an if. It's absolutely fine for the interface to require something that, given a particular state, it didn't actually need.

Thus, I'm absolutely fine with your interface being:

t.read(ul_data); 

regardless of the state of t or any booleans it contains. What's tying you in knots is your relationship with nothing. Nothing is a weird idea. Sometimes it seems very weird to get something and do nothing with it. But often that is exactly what you should do.

Use parameters to state needs but don't think of them as guarantied to be used every time. Sometimes they're just not.

What could cause trouble here is if there is no way to avoid this interface when you statically know that t doesn't need ul_data. There should be some way to get to the singleton data reading code without passing ul_data. But that doesn't mean this code needs to use it. This code is trying to not know the state of t. It would rather not know. Same as when you call x.multiply(2) you're trying to not know what x is now.

But when you already know what t really is you should have a way to avoid dealing with ul_data.

Why? For the same reason that this doesn't need a 2 or y passed to it.

assert(new X(0).getX() == 0); 
1
  • This is a big relief to me. I often feel uncomfortable with unused state, but it's actually fine. In my case, t objects are constructed at runtime based on user input. A t object may use either ul_data or l_data. Based on your answer, and John Wu's advice against the use of singleton in this case, I may want T to hold a state for l_data, it will be null if this t object actually uses ul_data. At the same time have the function "t.read" take ul_data as the argument. If the t object uses I_data, then the state will be used, if the t object uses ul_data, then the function argument is used.
    – DavidY
    CommentedFeb 7, 2020 at 13:56

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.