Oop

What is OOP?

Think of pure OOP as "objects talking to other objects"

Why use OOP?

OOP chiefly offers flexibility and extensibility

  • Once code is tested and delivered, it rarely has to be changed. There is little fear of new classes breaking old ones.
    • contrast this with procedural programming, which might involve adding new conditional checks for new features, which would require tests to be written, and carry a high degree of risk that original parts of the program will break.

OOP is for the next programmer, not for yourself or for performance. Secondly, when you need to start dynamically customising behaviour (changing class methods on the fly), it becomes more like writing little programs in their own right that interact. OOP even epitomises the unix philosophy (albeit without forcing interactions to be via text on the command line)

When OOP is used well it really clarifies the code, and is a solid way to break down complex algorithms into simpler pieces. A good set of classes is like a well run kitchen where everyone knows their own job, the communication is simple, and no one is stepping on anyone's toes.

  • A poor choice of classes ranges from confusing to a real mess depending on how liberally inheritance was used.

Deciding when it should be a class

The fact that something sounds like an object does not automatically mean that it should be an object in your program. Reflexively writing classes for every concept in your application tends to leave you with a collection of interconnected objects that each have their own internal, changing state. Such programs are often hard to understand and thus easy to break.

  • This is in reference to a program that simulates a village with roads between houses, and a robot that delivers parcels to each house. The program has to track the state of the parcel delivery status, as well as the robot's location in the town. Consider that if our instinct was to have classes for the robot, one for a parcel, maybe one for places, we would have to hold state in objects of different classes. The robot's current location would probably be held in the Robot class, while the delivery status of a parcel would probably be held in the Parcel class. Furthermore, a location would probably hold the state of all the parcels that are currently delivered to it. Having this state spread out everywhere makes the program hard to understand (and know which of these objects will need to have their state updated).

When considering how granular we should make our classes, it makes sense to think high-level. Ask the question, "how can I represent the state of this program in a central and simple way, where I can update the state in a single place in the event of a business-level action (such as moving location, dropping off a letter etc)". If we had a postman program, which mails a stack of letters to different householders, a natural inclination might be for us to create classes for postman, village etc. Instead, we should consider what the minimal scope of state is. In this example, we only really need to keep track of how many letters there are, and where the current location is. Realistically, all we need is a VillageState class.

class VillageState {
  constructor(place, parcels) {
    this.place = place;
    this.parcels = parcels;
  }

  move(destination) {
    if (!roadGraph[this.place].includes(destination)) {
      return this;
    } else {
      let parcels = this.parcels
        .map(p => {
          if (p.place != this.place) return p;
          return {place: destination, address: p.address};
        })
        .filter(p => p.place != p.address);
      return new VillageState(destination, parcels);
    }
  }
}

Let's apply the above lesson to TicTacToe. If we were to blindly make every item into it's own object, we would have classes for Game, Board, Player, Square. Resisting that temptation, think about how we might go about centralizing the state and the methods that change them. For instance, we don't really need a Square class. The duty of a Square class presumably is to keep track of the square's current state (empty, X, 0). But this could just as easily be managed by the same class that represents the Board. If we used a Square class, then we would have to reach into it every time we want to make a calculation about the state of the game.

  • spec: when deciding on what classes to make, think in terms of what state you need to store, and at what level would be appropriate?
    • ex. in TicTacToe, it might make sense to model a Player class, if you are going to use it to determine each player's currently occupied squares (ex. [0, 4, 7]). Alternatively, you may decide that that data can be safely stored in the Game object as 2 properties, player1Choices and player2Choices.

In OOP, dependencies are things that you need to provide to the constructor of the class to create an instance of it.


Example: Implementing TicTacToe

Create a player, game, and board class. The player class will choose moves, the board class will hold the state of the moves, and the game class will run the game and declare the winner.

If you want to make this project more challenging then make sure the game class can accept either human or computer players (and the code is exactly the same from the game class's perspective). This will allow you to practice class inheritance and also you can learn to build a simple ai. For the ai you can start with a simple computer that picks randomly. Once you get that done you can basically build a tree structure which builds all the possible future outcomes and make sure the computer selects the most favorable one.

E Resources

TicTacToe Repo in OOP: a great breakdown to understand OOP, without clutter of anything not related to TicTacToe


Children
  1. Composition
  2. Encapsulation
  3. Inheritance
  4. Keywords
  5. Overloading
  6. Overriding
  7. Polymorphism
  8. Substitutability

Backlinks