W1401 Object Oriented Design

From Coder Merlin

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 bindings are formulated in the object interface, followed by the interaction of the objects taking place. Before grasping the advanced and real life usage of objects, for basic familiarity and easy understanding, it's better to start with a physical example.

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.

GoingDeeperIcon.png
Going Deeper
  • 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 keyword class. It is customary to use PascalCase while defining objects (i.e. camelCase but with the first letter also capitalized). 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 with the type string.

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 for a subclass 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 Music, it will be possible to accept an array of strings as the input (in the initializer) 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.

Elaborating the concept of Inheritance with help of a diagram
// 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.

HintIcon.png
Helpful 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 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 a 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().

HintIcon.png
Helpful Hint

The parent class is sometimes also called as super class whereas the classes which inherits from a super class are called sub class while following set theoretic conventions.

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)
}

Comparison of Overload and Override[edit]

If we try to understand the concept behind overloading and overriding and its corresponding usages, the application of both will seem evident. As it is said, for overloading, the code of the concerned method or function remains same, only the argument varies in terms of quantity and type. Whereas overriding becomes useful where in a subclass if we need to use the a method with same arguments/parameters/properties and same return type as it was in the parent class but the operation involved needs some modification. In case of overload, the same method is used with varying arguments while performing same task as that of the parent class whereas in the case of method overriding, the method inherited from parent class is re-written to perform something specific to the subclass.

Instances[edit]

While the Piano class is the representation of an object, no real instantiation is done yet. When making instances, the 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

In this example, the instance piano is initiated and declared, with Piano being the class and piano being the corresponding instance. In general, the instances are not capitalized while the original class (type) is, obeying the convention. 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 a 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.

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 need to be overridden accordingly.

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

The guitar class is not specific enough, and it can be further classified as an Acoustic or Electric guitar and thus implies that the class Guitar itself is not 'concrete' enough but rather it is abstract. Similar the when it was mentioned about 'Instrument', only the degree of generality has been narrowed down, and the Guitar class is still abstract in nature.

HintIcon.png
Helpful Hint

The Swift language allows you to create abstract classes even when creating subclass without any special mention. Some other languages have a syntax which 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 AcousticGuitar class generates the class for a more specific kind of guitar and it can thus be called a concrete class. Since the values of the properties numberOfStrings and fretCount are 20 and 6 respectively for any kind of Acoustic guitars, they have been declared as static and in this case are also constants. The class automatically inherits all the properties and methods of the class Guitar accordingly.

Polymorphism[edit]

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 the object oriented programming paradigm. As we have seen before with method overloading, we can pass different set of parameters to the same method to use it in different mode as per specific localized usages. Similarly with method overriding, we have seen that the code details for a method has been changed where the parameters and method name and return type remains same. This feature is what we mean by saying polymorphism.

  • The rationale behind the concept of polymorphism is powerful and useful

Consider in real world- there are different instruments like violins, guitars and sarods. We know when it comes to playing or performing the specific instrument, the way of doing so differs but we play or perform which action in semantics remains just the same, though the way we do it becomes different. So, basically polymorphism is not some alien concept, rather it is a concept that is derived from real life which happened to help a lot while solving problems.

  • Why and how polymorphism is powerful

To understand where polymorphism gives us the extra edge, consider the structural programming approach where we need to define the objects like Violin, Guitar, Sarod etc which all are string based instruments and therefore all need to be tuned, but since they are different instruments, the tuning methods are different as well. In that case, the tune method for the individual objects would have been needed to be declared and specified differently. So, this diagram would make us understand the same.

Basic Programming Approach.png

Now, with OOP as we know we have created a parent class Instrument having the common methods and traits of the objects that would be derived from. After that we do the necessary overloading and overriding of the inherited methods according to the need of the specific object. The following diagram may explain this with more clarity.

OOP Approach.png

It can be understood that since the concept of polymorphism is derived from real life, while tackling real live problems in software engineering, polymorphism helps a lot while reducing the complexity of the project at large which would have been hefty and cumbersome otherwise.

  • Here we will see an example with code:
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 has been defined with the property type of Instrument, which means it stores the array of objects that have the type of Instrument. 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 although 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.

Abstraction[edit]

There is a utility in programming paradigm including Swift, whereby using abstraction we can control what information from a class we want to show and what we want to hide. To detail, suppose a trait of the class Instrument needs to be hidden, abstraction makes this possible. To provide a simple example, let's consider a physical Piano, where we see that we are prohibited from seeing the internal details of a piano; instead, we just see the outer visual of piano without any details of its composition. Similarly, while constructing a class, with abstraction the accessibility of the traits can be defined. It may be asked why abstraction is important and how it is useful. To answer that question, we should understand what kind of problem we solve with OOP approach. In such cases, it becomes necessary to hide some properties of an object from the end users due to security reasons and to keep the integrity of the system. We do this with the tool of abstraction.

Let us see the differences between Abstraction and Encapsulation:

Abstraction Encapsulation
Abstraction addresses the issue at the stage of Designing Encapsulation has its scope at implementation level
Abstraction involves the way to show the essential information while hiding unnecessary information Encapsulation is the process to combine the code and its associated data
The primary concern addressed with abstraction is what information an object will contain Encapsulation is the way to isolate information at the class and how the class methods will behave for security reasons

Information hiding[edit]

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

  • public: the information can be seen and accessed from outside the concerned object/module
  • private: the concerned information will only be accessible within that class
  • fileprivate: the information is allowed to be accessed only within the concerned file
  • internal: the information can be used in the files that define the module
  • open: can be used outside the concerned module, but can also be overridden or subclassed from outside
  • final: defines the property, method, or class in such a way that it cannot be overridden

To illustrate the implementation of information hiding, we will consider another parent class or super class which is amplifier.

class Amplifier {

  private var _volume: Int

  private(set) var isOn: Bool

  init() {
    isOn = false
    _volume = 0
  }


  func plugIn() {
    isOn = true
  }

  func unplug() {
    isOn = false
  }


  var volume: Int {
   
    get {
      return isOn ? _volume : 0
    }

    set {
      _volume = min(max(newValue, 0), 10)
    }
  }
}

In this code segment, a parent class or super class has been implemented, just like with the Instrument class, with the name Amplifier and representing the amplifier object. Within this amplifier class, a variable of integer type has been defined. But there is an underscore as a prefix to 'volume' which indicates it as private variable. While it is not mandatory to put 'underscore' while declaring private variable it is customary with some programming languages. When a variable is defined as private, it will be only accessible from within the class itself and not from the outside. In this example, the information specific to amplifier class if kept hidden. Though there is a finer usage of private. the variable 'isOn' for example is declared with the keyword 'private(set)'. The difference is where in case of '_volume' the information is completely hidden from outside of the class, in case of 'isOn' the information stored with it can be read but it can not be written from outside. Whereas two methods unplug() and plugIn() are the functions to modify the state of the variable 'volume'. An interesting declaration is done with the commuted property 'volume' which wraps the private variable '_volume'. With 'get' and 'set' the volume of the amplifier is regulated depending on the selected mode.

Key Concepts[edit]

KeyConceptsIcon.png
Key Concepts
  • Object Oriented Design or Object Oriented Programming is the approach where we break down a problem into several modules and develop the solution.
  • Class is the shared representation of a set of similar objects.
  • When defining the class, the relevant information (data, variable etc) and code (method, function etc) are combined in a special way which is known as encapsulation.
  • Sub classes inherit the properties like variable, constants, methods etc from the parent class. This is called inheritance which reduces redundant coding.
  • Methods in sub class may be overloaded and overridden. In overload, the same code segment operates on varrying parameters, whereas in case of overriding, the code block gets changed according to the specificity of the sub class.
  • With declaring an instance, a certain object can be given a usable form.
  • As in the cases of method overload and method override, the same method, without changing its usable form can be used in a different manner to achieve the required functionality. The feature of object oriented design where apparently the same thing exhibits different results, is called polymorphism.
  • With abstraction we can show only the essential information while hiding the unnecessary information.
  • There are different avenues to control the accessibility of the information namely private, public, fileprivate, internal, open and final.

Quiz[edit]

1 { What is the best definition of Class:

Parent of an Object
Instance of an object
Scope of an Object

2 Which one among the following does not have a body:

An Interface
An Abstract Method
A Class

3 Which of following is an shared representation of set of similar object :

A Class
Encapsulation
Inheritance

4 What Public, Private, Open etc are called:

Access Modifier
Class
Method SIgnature

5 When class property is declared private, it will be visible to :

The class and the sub classes
Only to the class
Everyone

6 Which of following are not related to Object Oriented Design:

Inheritance and Polymorphism
Class and Object
Structure and Union

7 Which of following have 'code re-usability' as its main feature :

Encapsulation
Polymorphism
Inheritence
Abstraction

8 Which of following are not features of Object Oriented Design:

Code Re-usability
Modularity
Data Redundancy

9 What is the maximum number of classes permissible in swift :

598
999
As many as you want

10 Which of following is not a type of class:

Base Class
Start Class
Abstract Class

11 Which of following is called generic class:

Abstract Class
Base Class
Template class

12 Instance of which of the following class type can not be created:

Abstract Class
Anonymous Class
Parent Class

13 A program in Swift can be written without declaring any class:

True
False

14 Keeping the name and code segment of the method same and changing the type and quantity of the argument is called:

Method Overloading
Method Overriding

15 Changing the code segment of the same method but keeping the arguments and return type same is known as:

Method Overriding
Method Overloading


Exercises[edit]

ExercisesIcon.png
Exercises
  1. Write the code for creating a Yamaha brand acoustic guitar. Then tune it and play it.


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.


Designed with pride in Silicon Valley, CA, USA