fingia
fingia

Reputation: 648

Parameterize property type using generics

I am trying to parameterize the type of the class property employee. It may be that generics is not the right tool to solve this problem, but wanted to see this problem could be solved using them, anyway.

Let's say an employee can be of type Clincal or Administrative.

class EmployeeStateController {
  var employee: Employee<Type>?
}

What I am wanting to do is to be able to initialize the state controller without knowing the employee type. The code above will not compile because Type is an unknown type. In any case, it would compile if I did this:

class EmployeeStateController<Type> {
  var employee: Employee<Type>?
}

But I do not want this since I DO NOT know Type when EmployeeStateController is instantiated. That is, I want to be able to do this:

// view controller 1: does not know the employee type just yet. 
let stateController = EmployeeStateController() // employee remains nil for now

// view controller 2: knows the employee type so it can assign the employee type. 
stateController.employee = Employee<Clinical>(name: "SomeName")

Upvotes: 1

Views: 49

Answers (2)

kid_x
kid_x

Reputation: 1475

The problem is that Type is part of type definition of EmployeeStateController. You can't initialize an object whose type you don't know.

One idea is to make Employee conform to some protocol.

protocol EmployeeProtocol {}

struct Employee<Type>: EmployeeProtocol { ... }

Then you can initialize your class with the protocol instead:

class EmployeeStateController {
    var employee: EmployeeProtocol
}

You can expand on this idea using type erasure if you can make some assertions about Type:

Make a protocol your Type will conform too, and constrain EmployeeProtocol's Type to this protocol.

protocol JobDescribable {
    var jobDescription: String { get }
}

// a Self-constrained protocol that refines Type

protocol EmployeeProtcool {
    associatedType Type: JobDescribable
    var type: Type { get }
}

struct Employee<Type: JobDescribable>: EmployeeProtocol {
    var type: Type
}

Create a type-erased Type (AnyJobDescribable) and a type-erased EmployeeProtocol object (AnyEmployee), which can wrap any object that conforms to EmployeeProtocol.

// Type-erased object that will serve as Type for the type-erased AnyEmployee

struct AnyJobDescribable: JobDescribable {
    let describable: JobDescribable
    var jobDescription: String { describable.jobDescription }
}

// AnyEmployee can be initialized by any EmployeProtocol type, including Employee

struct AnyEmployee: EmployeeProtocol {
    var type: AnyJobDescribable
    private var employee: Any

    init<SpecializedEmployee: EmployeeProtocol>(employee: SpecializedEmployee) {
         self.employee = empyloee
         type = AnyJobDescribable(describable: employee.type)
    }
}

Using this single AnyEmployee object, you can now create an EmployeeStateController that doesn't have to know about the different Employee objects you want to use.

// Using AnyEmployee, EmployeeStateController can use any type of Employee 
// (or EmployeeProtocol) without requiring a generic type in its definition

class EmployeeStateController {
    var employee: AnyEmployee?
}

Depending on what you're trying to do, this might not be worth it. Using employee as a protocol without an associatedtype (like in the first example) is much more straightforward.

Upvotes: 1

David Pasztor
David Pasztor

Reputation: 54706

You cannot do that. All types must be known at compile time and the type of a variable cannot change after the type was instantiated.

The closest you can get is to make Employee a protocol and assign a concrete conforming type when you know the type.

protocol Employee {
    var name: String { get }
    init(name: String)
}

struct Clinical: Employee {
    let name: String

    init(name: String) {
        self.name = name
    }
}

class EmployeeStateController {
  var employee: Employee?
}

// view controller 1: does not know the employee type just yet.
let stateController = EmployeeStateController() // employee remains nil for now

// view controller 2: knows the employee type so it can assign the employee type.
stateController.employee = Clinical(name: "SomeName")

Upvotes: 2

Related Questions