W1401 Object Oriented Design

From Coder Merlin
Within these castle walls be forged Mavens of Computer Science ...
— Merlin, The Coder

Introduction[edit]

Object Oriented Design is a problem solving approach in the programming paradigm where an object oriented approach is followed. In other words, object oriented design is a way of breaking down distinct aspects of a problem into modules called objects, and developing a solution by making those objects interact with each other by sending messages or other information. This approach is adopted by many programming languages, not only Swift, but also in Java, JavaScript, C#, C++, Python, Ruby, etc. and dates back to the 1950s. Object oriented design is considered to be one of the most efficient ways of problem solving in programming, while another major paradigm is a structured programming approach. While structures programming is considered task-centric, object oriented design is known to be data-centric.

In object oriented design, the objects are used to model different components of the problem which ranges from the physical things like a book, a touch on a screen, or a database to more complex representations of abstraction like the daily petrol price, fluctuation of interest rates, etc.

Definition of Object[edit]

An object is the encapsulated representation of an entity which combines the data and associated procedures. The rules and binding ways are formulated in object interface following which the interaction of the objects takes place. Before grasping the advanced and real life usage of objects, for basic familiarity and easy understanding, it's better to start with the physical things like taking it literally.

Generally, in object oriented designs, the objects are designed to describe a set of general concepts and then extending to more specific types. Start with the concept of musical instrument. After defining a musical instrument as an object, you can create (in abstraction) the extended concept of a musical instrument in the form of particular instances like Piano, Viola, Violin, Guitar, Electric Guitar etc. which are more specific kinds but can be derived from the same parent object.

Going DeeperGoingDeeperIcon.png
  • The usage of the word 'object' in the programming paradigm has its genesis back in late 1950 within the group of Scientists working for Artificial Intelligence based on the LISP programming Language at MIT.

Initializing Objects in Swift[edit]

Properties[edit]

// Example of declaring a class
class Instrument {
  // An uninitialized property
  let brand: string
  // Constructor method
  init(brand: string) {
    // Initializing the brand property
    self.brand = brand
  }
}

In this example, we have declared the base object as Instrument. The object has been declared and defined with the key word class. It is customary to use capitalization while defining objects. While it is not mandatory to always capitalize the class name, it is a general convention which is followed. While defining the object Instrument, some properties should be defined which are common for all the instances that we will derive from this object in future such as piano, guitar, electric guitar, etc. Here the property of 'brand' has been declared and defined as the string type. With this definition process the initialization of object 'Instrument' was successfully carried out.

Encapsulation[edit]

Methods[edit]

After defining properties for the object, the necessary actions should be implemented via adding methods.

func tune() -> String {
  fatalError("Implement this method for \(brand)")
}

In this example, the tune() method is a placeholder function which can cause crashes when called. Methods like this are often called abstract since they are very generic and not intended for direct usage. Rather than merely calling the function fatalError(), it is expected to create a subclass of such method to override the method in order to do something sensible or more specific.

Functions that are defined under a class are called methods since they have the access to the corresponding class's properties. Here, the tuning method has the access to the property brand. This way of organizing the properties and associated functions together is the basis of why object oriented design has been successful in tackling highly complex problems. As such, combining properties and their associated functions is called Encapsulation. In other words, class types are known to encapsulate the data (stored properties) and behavior (methods).

class Music {
  let notes: [String]

  init(notes: [String]) {
    self.notes = notes
  }

  func prepared() -> String {
    return notes.joined(separator: " ")
  }
}
func play(_ music: Music) -> String {
  return music.prepared()
}

With creating the new class Playmusic, it will be possible to accept an array of strings as the input and the string output in flattened form with the help of the prepared() method. Also, the play() function allows us to have the String which needs to be played. It's always better to create a class of Music than to just to pass an array of strings by this class; the function calling and properties remain organized and it also gives the scope to expand this class as needed in future.

func perform(_ music: Music) {
  print(tune())
  print(play(music))
}

The perform(_:) method is a a composite method which combines two methods, namely the tune() method and the play() method, in order to tune and play at the same time.

Inheritance[edit]

In object oriented design, inheritance is the way of going from a general base class to a more specific subclass.

// The class Piano is defined as a subclass of Instrument
class Piano: Instrument {
  let hasPedals: Bool
  // The static properties whiteKeys and blackKeys are defined and initialized
  static let whiteKeys = 52
  static let blackKeys = 36
  
  // The initializer method is defined
  init(brand: String, hasPedals: Bool = false) {
    self.hasPedals = hasPedals
    // The parent class's initializer is called
    super.init(brand: brand)
  }
  
  // The parent class's tune method is overridden
  override func tune() -> String {
    return "Piano standard tuning for \(brand)."
  }
  
  override func play(_ music: Music) -> String {
    // The parent class's play method is called 
    let preparedNotes = super.play(music)
    return "Piano playing \(preparedNotes)"
  }
}

Here, the Piano object has been initialized as a subclass of parent class Instrument. All of the properties and methods are inherited into the child class Piano. From the real life situation, since all the pianos should always have exactly the same number of black and white keys, the declaration of the corresponding properties have been made static.

With the help of an initializer, the default value of the parameter hasPedals is set, which also has the option to keep it off. The super keyword is used to call the parent class, which in this case is Instrument and it is done after the initialization of the child class property hasPedals. With the use of the super class initializer we are able to initialize the inherited property, which in this case is brand.

Method Override[edit]

With the use of the keyword override, the inherited tune() method's implementation is overridden. By doing so, we are successfully able to make tune() execute something specific to the Piano class while not simply calling fatalError(). The method play(_:)is also overridden here. The Instrument parent method is called here with the usage of super keyword so that the prepared notes can be obtained and then played. Since Piano is subclass of Instrument, it can be presumed without any error that it has brand as its property and the corresponding methods (behaviors) as tune(), play() and perform().

Hint.pngHelpful Hint

The initialization process used by Swift classes are always two-phase-initialization in order to ensure that all the properties are initialized before using them.

Method Overload[edit]

Method overloading is the change in parameters (both in numbers and types) in the method for getting the intended results.

func play(_ music: Music, usingPedals: Bool) -> String {
  let preparedNotes = super.play(music)
  if hasPedals && usingPedals {
    return "Play piano notes \(preparedNotes) with pedals."
  }
  else {
    return "Play piano notes \(preparedNotes) without pedals."
  }
}

When this method is added to Piano class, the actual play(_:) method gets overloaded resulting the situation where the Piano will be played only if usingPedals is set to true, meaning the Piano has pedals to use. When the method play(_:) is called, Swift uses the parameter list also known as signatures to understand whether or not the original method is called or its overloaded method. The play(_:) method can be overridden by replacing the original play(_:) method in Piano class as following:

override func play(_ music: Music) -> String {
  return play(music, usingPedals: hasPedals)
}

Instances[edit]

While Piano class is the representation of the object, no real instantiation is done yet. With making instances actual instantiation of an object is done.

let piano = Piano(brand: "Yamaha", hasPedals: true)
piano.tune()
let music = Music(notes: ["C", "G", "F"])
piano.play(music, usingPedals: false)
piano.play(music)
Piano.whiteKeys
Piano.blackKeys

The instance piano is initiated and declared. While Piano is the class and piano is the corresponding instance. In general, the instances are not capitalized while the original class (type) are capitalized obeying the conversion. Then the instance music is created of the class Music, which in turn was used in the overloaded play function through which the piano was played without pedal. The actual play(_:) method was called then without any overload, which tries to play piano always using pedals. The variables whiteKeys and blackKeys are already declared in the parent Piano class, hence the same is not needed to be instantiated again.

Abstract Base Class[edit]

In following snippet of codes, another instance of the base class Instrument has been declared which is Guitar class. Here stringGauge is a new variable of the type string which is specific to this class while other properties and methods of Instrument class has been inherited and requires to be overridden accordingly.

class Guitar: Instrument {
  let stringGauge: String
  
  init(brand: String, stringGauge: String = "medium") {
    self.stringGauge = stringGauge
    super.init(brand: brand)
  }
}

So, this guitar class is not enough specified, and it can be further classified as Acoustic guitar or Electric guitar and thus implies the class Guitar itself is not enough 'concrete' rather is abstract like what it was mentioned about 'Instrument', only the degree of generality has been narrowed down but still the guitar class is abstract in nature. Such an class is often called intermediate abstract base class.

Hint.pngHelpful Hint

Swift language allows you to create abstract classes even when creating subclass without any special mention whereas there are other language who syntax may require you to declare explicitly while creating an abstract base class.

Concrete Class[edit]

class AcousticGuitar: Guitar {
  static let numberOfStrings = 6
  static let fretCount = 20
  
  override func tune() -> String {
    return "Tune \(brand) acoustic with E A D G B E"
  }
  
  override func play(_ music: Music) -> String {
    let preparedNotes = super.play(music)
    return "Play folk tune on frets \(preparedNotes)."
  }
}

This Acoustic Guitar class generates the class for the more specific kind of class and thus can be said as Concrete class. Since the values of the two properties of numberOfStrings and fretCount are 20 and 6 respectively for all kind of Acoustic guitars, they have been declared as static and thus as Constants. This class automatically inherits all the properties and methods of the class Guitar accordingly.

Polymorphism[edit]

By definition polymorphism means how the same thing can exhibit different properties or can be witnessed or found differently. Polymorphism is one of the greatest strengths of OOP oriented programming paradigm.

class Band {
  let instruments: [Instrument]
  
  init(instruments: [Instrument]) {
    self.instruments = instruments
  }
  
  func perform(_ music: Music) {
    for instrument in instruments {
      instrument.perform(music)
    }
  }
}

let instruments = [piano, acousticGuitar, electricGuitar, bassGuitar]
let band = Band(instruments: instruments)
band.perform(music)

Here with defining the band class, the array instruments have been defined with the property type of Instrument, which means it stores the array of objects that have the type of Instrument class. In the later part of the code, the perform(:_) functioned is invoked within the for loop where it plays different objects from the instrument array. The noteworthy point here is that though all the components of the array instrument is of the type Instrument, each of these objects are invoked according to the type of the corresponding class. This is an epitome of polymorphism in swift.

Information hiding[edit]

In object oriented designs and programming paradigm, there are different ways of control of information which becomes really helpful while tackling large problems and modularization the same. In simple words, information hiding is the process by virtue of which the programmer can assign better control on the information used by different objects. Here are some examples of some of such options that Swift allows:

  • Public: it is the access control in which the information can be seen and accessed from outside the concerned object/module.
  • Private: in this kind of access control, the concerned information will only be accessible within that class itself.
  • fileprivate: fileprivate is somewhere in between private and public where the information is allowed to be accessed only within the concerned file.
  • internal: internal and private may seem similar at first appearance, but in case of internal, the concept of module is extended to app which is class in case of private.
  • open: information or property or method when declared as open, while can be used outside the concerned module, at the same time can be overridden or subclassed from outside as well.
  • final: the final access control is used to eliminate the scope of overriding.

Exercises[edit]

ExercisesExercisesIcon.png
  1. Write the code for creating a Yamaha brand acoustic guitar. Then tune it and play it.
  2. Second
  3. Third


References[edit]

  • Object Oriented Programming (Ray Wenderlich)
  • OOP (github.com/cajyofendhi)
  • Swift Object Oriented Programming for Beginner: Swift Object Oriented Programming for Beginner. N.p., GAMADEV, 2019.
  • Hillar, Gaston C.. Swift 3 Object-Oriented Programming. United Kingdom, Packt Publishing, 2017.
  • Hillar, Gaston C.. Object–Oriented Programming with Swift 2. United Kingdom, Packt Publishing, 2016.
  • Pitre, Boisy G.. Swift for Beginners: Develop and Design. N.p., Pearson Education, 2014.