Search code examples
swiftclassprotocols

Protocols and classes in Swift


I have a task that says I need to write an implementation of four universal methods in the ShopImpl class.

If I was working with arrays, for example, or with dictionaries, I would write an implementation of the methods, but in this case I just don't understand where, for example, I need to add Product in the addNewProduct(product: Product) method.

The task says, for example, that it is necessary to implement a method that adds a new object to the Shop. But the Shop is a protocol. How can I add something to the protocol. Please help me with understanding what the implementation of the universal addNewProduct(product: Product) method should look like in this case.

/**
 The task is to implement the Shop protocol (you can do that in this file directly).
 - No database or any other storage is required, just store data in memory
 - No any smart search, use String method contains (case sensitive/insensitive - does not matter)
 */

struct Product {
    let id: String; // unique identifier
    let name: String;
    let producer: String;
}

protocol Shop {
    /**
     Adds a new product object to the Shop.
     - Parameter product: product to add to the Shop
     - Returns: false if the product with same id already exists in the Shop, true – otherwise.
     */
    func addNewProduct(product: Product) -> Bool
    
    /**
     Deletes the product with the specified id from the Shop.
     - Returns: true if the product with same id existed in the Shop, false – otherwise.
     */
    func deleteProduct(id: String) -> Bool
    
    /**
     - Returns: 10 product names containing the specified string.
     If there are several products with the same name, producer's name is added to product's name in the format "<producer> - <product>",
     otherwise returns simply "<product>".
     */
    func listProductsByName(searchString: String) -> Set<String>
    
    /**
     - Returns: 10 product names whose producer contains the specified string,
     result is ordered by producers.
     */
    func listProductsByProducer(searchString: String) -> [String]
}

// TODO: your implementation goes here
class ShopImpl: Shop {
    func addNewProduct(product: Product) -> Bool {
        // your code
    }
    
    func deleteProduct(id: String) -> Bool {
        // your code
    }
    
    func listProductsByName(searchString: String) -> Set<String> {
        // your code
    }
    
    func listProductsByProducer(searchString: String) -> [String] {
        // your code
    }
    
}

Btw, tests for above methods look this way:

func test(lib: Shop) {
    assert(!lib.deleteProduct(id: "1"))
    assert(lib.addNewProduct(product: Product(id: "1", name: "1", producer: "Lex")))
    assert(!lib.addNewProduct(product: Product(id: "1", name: "any name because we check id only", producer: "any producer")))
    assert(lib.deleteProduct(id: "1"))
    assert(lib.addNewProduct(product: Product(id: "3", name: "Some Product3", producer: "Some Producer2")))
    assert(lib.addNewProduct(product: Product(id: "4", name: "Some Product1", producer: "Some Producer3")))
    assert(lib.addNewProduct(product: Product(id: "2", name: "Some Product2", producer: "Some Producer2")))
    assert(lib.addNewProduct(product: Product(id: "1", name: "Some Product1", producer: "Some Producer1")))
    assert(lib.addNewProduct(product: Product(id: "5", name: "Other Product5", producer: "Other Producer4")))
    assert(lib.addNewProduct(product: Product(id: "6", name: "Other Product6", producer: "Other Producer4")))
    assert(lib.addNewProduct(product: Product(id: "7", name: "Other Product7", producer: "Other Producer4")))
    assert(lib.addNewProduct(product: Product(id: "8", name: "Other Product8", producer: "Other Producer4")))
    assert(lib.addNewProduct(product: Product(id: "9", name: "Other Product9", producer: "Other Producer4")))
    assert(lib.addNewProduct(product: Product(id: "10", name: "Other Product10", producer: "Other Producer4")))
    assert(lib.addNewProduct(product: Product(id: "11", name: "Other Product11", producer: "Other Producer4")))
    
    var byNames: Set<String> = lib.listProductsByName(searchString: "Product")
    assert(byNames.count == 10)
    
    byNames = lib.listProductsByName(searchString: "Some Product")
    assert(byNames.count == 4)
    assert(byNames.contains("Some Producer3 - Some Product1"))
    assert(byNames.contains("Some Product2"))
    assert(byNames.contains("Some Product3"))
    assert(!byNames.contains("Some Product1"))
    assert(byNames.contains("Some Producer1 - Some Product1"))
    
    var byProducer: [String] = lib.listProductsByProducer(searchString: "Producer")
    assert(byProducer.count == 10)

    byProducer = lib.listProductsByProducer(searchString: "Some Producer")
    assert(byProducer.count == 4)
    assert(byProducer[0] == "Some Product1")
    assert(byProducer[1] == "Some Product2" || byProducer[1] == "Some Product3")
    assert(byProducer[2] == "Some Product2" || byProducer[2] == "Some Product3")
    assert(byProducer[3] == "Some Product1")
}

test(lib: ShopImpl())

Solution

  • The idea of that design is to define a protocol that is independent of any particular implementation. That's called an abstraction.

    The Shop protocol defines an operation that must be implemented by a any shop. You may chose to implement your shop in memory using an array or a dictionary; but you may as well chose to implement a persistent shop using some data base or let the shop outsource the list of products to another class. The key idea is that the classes/object that use the Shop may add a product to a shop, regardless of any specific implementation choice. So the same code would work with an array-based shop implementation or a database based implementation.

    Of course, the Shop protocol does not do anything by itself. It only defines a "contract", i.e. what methods with what arguments and what behavior. You therefore need a class (or a structure) ShopImpl. When we say that Shop adds a product to the shop, it's not the protocol that adds something: it's understood that it is an object of a class/struct that complies with the Shop protocol.

    You can visualise this as follows:
    enter image description here

    Your code would then look like:

    var aShop : Shop      // aShop is a shop 
    aShop = ShopImpl()    // we intantiate aShop using a shop implementation. 
    //aShop = DBShopImpl(dbserver:"www.example.com/db")
    let p1 = Product(id:1)
    let p2 = Product(id:2) 
    aShop.addNewProduct(product:p1) 
    aShop.addNewProduct(product:p2) 
    

    As you see, I could use another shop implementation. We are flexible as long as the class used complies with the Shop.