0

i'm an iOS dev with a couple of years of experience with swift, but rarely i've used PAT's...

This time, I was trying to move some code from an app that i've developed to a shared library that I use in a couple of projects. The case is about a Factory that uses various Builders (that are decorators of my business resources) via an Abstract Builder protocol, to obtain Items (in the real case, ViewControllers).

The Builder relays upon some variables that the Factory passes to him, but those are at the application level, so, to extract this logic and put it into my library, i need to use a generic reference, and because I want to work in a Protocol Oriented Programming manner, it is an AssociatedType.

// The item that i want to receive from my factory
protocol Item { 
    var content: String { get }
}

// This is the Builder interface that the Factory consumes
protocol Builder {  

    // The Abstract Parameters that the Application should define
    associatedtype Parameters

    func build(_ parameters: Parameters) -> Item?
}

// The BusinessResource of my library 
protocol BusinessResource { }

// The Factory that consumes the Builders
protocol Factory {

    associatedtype FactoryBuilder: Builder

    var parameters: FactoryBuilder.Parameters { get }

    func make(from businessResource: BusinessResource) -> Item?
}

// The generic implementation of my Factory
extension Factory {

    func make(from businessResource: BusinessResource) -> Item? {

        guard let builder = businessResource as? FactoryBuilder else {
            return nil
        }
        return builder.build(self.parameters)
    }
}

At this point everything looks good.

I have two protocols and those are binded together, sharing a common type who is generic (the Builder Parameters).

So, on the application layer, now i could introduce my concrete Parameters (i'll call them ConcreteParameters XD)

// The concrete parameters of the Application Factory
struct ConcreteParameters {
    let string: String
}

// The Builder interface restricting Parameters to ConcreteParameters 
protocol BindedBuilder: Builder where Parameters == ConcreteParameters {

}

// The Factory interface restricting Parameters to ConcreteParameters 
protocol BindedFactory: AbstractFactory where FactoryParameters: ConcreteParameters {

}

So far, so good. Everything looks in place and I'm start thinking that this could work, so now i try to implement a concrete Factory on the application to try if this really works.

// The concrete output of my Builder
struct ConcreteItem: Item {
    var content: String
}

// The concrete BusinessResource that i get from my library
struct ConcreteObject: BusinessResource {

    let string: String
}

// The decoration extension that makes ConcreteObject compliant with Builder
extension ConcreteObject: Builder {

    typealias Parameters = ConcreteParameters

    func build(_ parameters: ConcreteParameters) -> Item? {
        return ConcreteItem(content: parameters.string + self.string)
    }
}

// The real Factory inside my app
class ConcreteFactory: BindedFactory {

    typealias FactoryBuilder = BindedBuilder

    var parameters: ConcreteParameters {
        return ConcreteParameters(string: "Hello ")
    }
}

let item = ConcreteFactory().make(from: ConcreteObject(string: "world!"))
print(item ?? "NOT WORKING")

At this point something breaks... I get this error:

Error: Type ConcreteFactory does not conform to AbstractFactory

[EDIT: Error came from a previous version of the snippet, AbstractFactori is current Factory]

It is a Bug?? I really don't know how to solve this...

Oni_01
  • 470
  • 5
  • 12
  • Hard to see what's going on without knowing what `AbstractFactory` is - could you add the code where that's defined? Also, I don't want to be that guy who gives you unsolicited advice that doesn't actually solve your stated problem. But: don't do what you're doing above. I've been down that path and done the same. Protocol Oriented Programming isn't something you should seek to do just as a principle. Used in the wrong places, it creates unreadable spaghetti code, like the above. Go for a method of working which doesn't require so much cognitive load to understand :) – sam-w Oct 06 '18 at 08:36
  • I'm sorry, the AbstractFactory was the old name of the factory before a refactor.. l'll double-check the snippets and fix problems where i could find. – Oni_01 Oct 06 '18 at 08:38
  • I can understand your position about Protocol Oriented Programming, and i know that this could appear as an overkill solution to a simple problem, but this is a little snippet from a much bigger framework based on protocol compositions, and this snippet, if works, could solve a problem on a core part of my framework – Oni_01 Oct 06 '18 at 08:44

1 Answers1

1

I think in this case you need to use a concrete type to alias FactoryBuilder instead of BindedBuilder, as protocols do not conform to themselves.

This code effectively compiles, would something like that match your requirements?

class ConcreteFactory: BindedFactory {
    typealias FactoryBuilder = ConcreteObject

    var parameters: ConcreteParameters {
        return ConcreteParameters(string: "Hello ")
    }
}

Otherwise you can also try type erasing BindedBuilder and create AnyBindedBuilder, as suggested in the same link.

Fran Pugl
  • 464
  • 5
  • 6
  • Yeah, that's the same solution that I've found. And looks like it's the only way to do it... so bad that what i wanted was to add conformance by an extension, for my particular case. In this way, i need a specific subclass to be conform. – Oni_01 Oct 19 '18 at 08:21