-1

Recently I refactored the parent class, let's call it Fish.

        public class Fish {
                 ...
    }

Fish had a lot of functions in it that made it logical to refactor for modularity, so I created a child class, let's call it Shark.

   public class Shark extends Fish { 
                 ...
}

There is a third class, very much only used as a sort of setup script class which might not be ideal, but it is this way for now. Let's call this third class SwimmingInTheOcean.

    public class SwimmingInTheOcean {
     public Fish nemo = new Fish();
     public Shark bruce = new Shark();
    
    public void func1() {
      nemo.move();
    }

    public void func2() {
      nemo.sleep();
    }

    public void func3() {
      ocean.setup...
      nemo.sleep();
      nemo.eat();
    }

    public void func4() {
      ocean.setup...
      nemo.sleep();
      print...
    }

    public void func5() {
      bruce.move()
    }

    public void func6() {
      bruce.eat()
      bruce.sleep()
    }
}

SwimmingInTheOcean creates an object of Fish, because the class has many functions that work on Fish. There is however, two functions in SwimmingInTheOcean which require an object of type Shark as well.

So far I see three options, but none of them seem ideal.

  1. Create both object type Fish and object type Shark in SwimmingInTheOcean. This is what I did.
  2. Create only an object type Shark, because shark already contains all the functions of Fish. The reason why I did not pick this solution however, is because for most intents it looks very weird to use a Shark instead of a Fish. It would mislead someone reading the code in the future.
  3. Split SwimmingInTheOcean as well into two, but the issue is, the higher level class where I sometimes would be using Shark, I still use Fish 90% of the time. So I would have to split that class as well into two.

This is probably a different issue altogether, but Fish has functions that I have not overloaded yet in Shark, instead I created new functions. Fish can move, eat, sleep, and Shark has sharkMove, sharkEat, sharkSleep instead of overloading. The reason why I haven't overloaded these yet is because the Shark functions still partially use the fish functions, so sharkMove still has the Fish' move as a step 1.

Alex Mm
  • 65
  • 1
  • 1
  • 7
  • Consider using a common Interface instead. Leave the implementation details to the concrete class. Your `SwimmingInTheOcean` only needs to call `swim()`, `eat()` etc. – Murat Karagöz Feb 16 '23 at 13:47

1 Answers1

1

The setup of your question indicates you have much bigger fish to fry, so to speak: Your code doesn't make any sense in the first place.

Specifically, this part:

Create both object type Fish and object type Shark in SwimmingInTheOcean. This is what I did.

It sounds like you're treating objects as being simply namespaces. Not, themselves, things that represent stuff and have actual state.

Let's say you really do have 2 sharks (bruce and sheila) and 1 fish (nemo).

Clearly, in such a situation, nemo.move() and bruce.move() do completely different things - specifically, they cause different state (namely, nemo's position for nemo.move(), and bruce's for bruce.move()) to be used in the calculations and to be affected by the method invocation. They aren't interchangible at all, and therefore, this:

There is however, two functions in SwimmingInTheOcean which require an object of type Shark as well.

Just doesn't make sense. Specifically, it doesn't make sense to see 'I have a function that I need to invoke; this will require an object of type Shark'. You don't "require an object so that some function is available", you "require a shark", and it's not specifically about Bruce's abilities. It's about poor bruce himself. If you need bruce, you need bruce.

Specifically, if the only thing you need bruce for, is things that any fish can do, okay. You still need bruce. Objects aren't interchangible, or, if they are, you've go some fairly bizarre design going on, not very OOP-style. If it works, it works, but this question is specifically about code style, and there is rather little point attempting to answer a question that boils down to:

"I am using this batch of paintbrushes as tools for stirring while I cook a meal. When using the pointed tip brush, should I, as a point of general cooking style, go with a coarse grained weave in the brush, or a fine grained one"? - That's a silly question. The proper question is: "What are proper cooking implements". If somehow brushes are all you have, worrying about making it look good is ridiculous.

Create both object type Fish and object type Shark in SwimmingInTheOcean. This is what I did.

It sounds like all the methods you are interested in are utility methods that do not read or otherwise affect any state except through the parameters you provide, and therefore, should be static, and once you've done that, this question has the obvious answer. Just invoke the methods you need, simple. No need to create any objects anymore.

Create only an object type Shark, because shark already contains all the functions of Fish. The reason why I did not pick this solution however, is because for most intents it looks very weird to use a Shark instead of a Fish. It would mislead someone reading the code in the future.

This is misleading. In java, there's the type of the object and the type of the reference which do not have to be the same thing. This is perfectly legal java code:

Fish fish = new Shark();

and very clearly communicates to any future readers that at the very least the API of the object is interesting solely for its fish-like properties; the fact that you picked a shark is presumably (as in, that's what a future reader would assume) relevant in the sense that the 'shark' implementation is the way you want the API to be implemented.

Here, for example, if you have 3 things you need done, and one of the 3 are things you can do with any fish (say, move them to the top of the aquarium so you can catch them with a net), whereas the other is 'found the fish friendly club' which is a thing only sharks can do, then you would make 2 methods. Your class is called SwimmingInTheOcean which is weird; normally, classes are named to match a tangible concept class, and 'swimmingInTheOcean' isn't it. Aquarium or Ocean, that makes sense as a name. You'd have:

class Ocean {
  void simulateDay() {
    Shark bruce = new Shark();
    foundFishFriendlyClub(bruce);
    chaseToTheNets(bruce);
  }

  void foundFishFriendlyClub(Shark leader) { ... }
  void chaseToTheNets(Fish fish) { .... }
}

Here there is no chance that a reader is going to be confused and thinks that somehow chasing into the nets is a thing that applies only to sharks. After all, whilst you call chaseToTheNets with a shark, all sharks are fishes, and the chaseToTheNets method yells from the rooftops you can do that to any Fish.

It's more correct to put the chaseToTheNets method in the Ocean class instead of the Fish class, because it's an act that a fish itself cannot do (if there is a FleetOfTrawlers class, the chaseToTheNets method should be there instead of in Ocean).

It's possible this advice misses the mark because it doesn't apply to your situation. However, if that is the case, your chosen example situation (with nemo and fishes and sharks and oceans) isn't a good way to summarize your situation.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72