Friday, January 15, 2016

Refactoring in Swift - Replace method with method object

Let's continue with the same game as in Explaining Variable refactoring method.

We still have the two ships, each of them defined as a rectangle (CGRect) but today they start shooting at each other.

So other than the ships we have two projectiles, also defined as rectangles.

We also have a number of functions to manage the game.



class Game {
    var myShipFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
    var myProjectileFrame = CGRect(x: 0, y: 0, width: 10, height: 2)
    
    var enemyShipFrame = CGRect(x: 50, y: 50, width: 100, height: 100)
    var enemyProjectileFrame = CGRect(x: 50, y: 50, width: 10, height: 2)
    
    func checkShipsCollided(myShipFrame:CGRect, enemyShipFrame:CGRect) -> Bool {
        //some calculations
        return true //or false
    }
    
    func checkShipIsDestroyed(shipFrame:CGRect, projectileFrame:CGRect) -> Bool {
        //some calculations
        return true //or false
    }
    
    func moveItemsOnTheScreen(){
        //move the ships and projectiles
        //...
    }
    
    func playGame() {
        if checkShipsCollided(myShipFrame, enemyShipFrame: enemyShipFrame) {
            print("End of game")
        } else if checkShipIsDestroyed(myShipFrame, projectileFrame: enemyProjectileFrame) {
            print("Enemy wins")
        } else if checkShipIsDestroyed(enemyShipFrame, projectileFrame: myProjectileFrame) {
            print("I win")
        } else {
            moveItemsOnTheScreen();
        }
    }
}

Now imagine that each of these functions have 100 lines. 

It is obvious this will be a huge class. If you look carefully, the two functions (checkShipsCollided and checkShipIsDestroyed) are totally independent of the Game class.

So we can move them somewhere else, for example to a GameUtils class:

class GameUtils {
    static func checkShipsCollided(myShipFrame:CGRect, enemyShipFrame:CGRect) -> Bool {
        //some calculations
        return true //or false
    }
    
    static func checkShipIsDestroyed(shipFrame:CGRect, projectileFrame:CGRect) -> Bool {
        //some calculations
        return true //or false
    }
}

In this case, our Game class is simpler and 200 lines shorter:

class Game {
    var myShipFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
    var myProjectileFrame = CGRect(x: 0, y: 0, width: 10, height: 2)
    
    var enemyShipFrame = CGRect(x: 50, y: 50, width: 100, height: 100)
    var enemyProjectileFrame = CGRect(x: 50, y: 50, width: 10, height: 2)
    
    
    func moveItemsOnTheScreen(){
        //move the ships and projectiles
        //...
    }
    
    func playGame() {
        if GameUtils.checkShipsCollided(myShipFrame, enemyShipFrame: enemyShipFrame) {
            print("End of game")
        } else if GameUtils.checkShipIsDestroyed(myShipFrame, projectileFrame: enemyProjectileFrame) {
            print("Enemy wins")
        } else if GameUtils.checkShipIsDestroyed(enemyShipFrame, projectileFrame: myProjectileFrame) {
            print("I win")
        } else {
            moveItemsOnTheScreen();
        }
    }
}

As a rule, one class as well as one function should do just one thing. This is called Single responsibility principle

Using this type of refactoring, we can be sure our code is following this principle and they are as small as possible.

No comments:

Post a Comment