Friend class

Is it possible restrict access to constructor init() to unrelated class? I want to implement factory, that will create instances of a class, but restrict creation of this class directly.

class Foo {
	private init() { }
}

class FooFactory {
	static createFoo() -> Foo { Foo() } // Allow it here?
}

usage:
FooFactory.createFoo()

Basically, I'm looking for C++ friend analogue. I know, that it is not exist in swift, but my goal is to ensure, that all instances of a class (and subclasses) were created using factory.

Or, if it's not possible for constructor, can it be possible for any other function?
class Foo {
private func run() { }
}

class FooLauncher {
	static func launch(foo: Foo) {
		foo.run()
	}
}

Sorry, if it's a newbie question, I came from C++ and C# development and trying to implement familiar patterns on a new language :)

Swift doesn't have friend, but this is the use-case for fileprivate.

At a higher level, though, it might be reasonable to ask whether factories are the right pattern for what you're trying to do, as opposed to e.g. first-class functions or something similar.

2 Likes

I like nesting my builder types inside my model types. They can use the same generic variables, get a nice convenient namespace, and have access to the internals.

Generally, I don't like Factories being the only entry-point into a class/struct. Instead, I would make an initializer that takes all fields as params, and validates them. Ultimately, the factory calls this initializer.

Here's a basic example:

struct Size {
	let x: Int
	let y: Int
	
	init?(x: Int, y: Int) {
		guard 0 <= x, 0 <= y else { return nil } // 4. Ultimate validation logic goes here
		self.x = x
		self.y = y
	}

	struct Builder {
		var x: Int? = nil
		var y: Int? = nil
		// ... and many other properties, otherwise a builder isn't even worth it
		
		init() {}
		
		func build() -> Size? {
			// 3. Validate that all the necessary things are present
			guard
				let x = self.x,
				let y = self.y
			else {
				return nil
			}
			
			return Size(x: x, y: y)
		}
	}
}

var builder = Size.Builder() // 1. Always-required properties could be required in this initalizer here, up-front

// 2. Manditory (but not up front) fields could be set here
builder.x = 123
builder.y = 456

let myPoint = builder.build()
print(myPoint as Any) // 5. Ta-da, a new point
2 Likes

I simplified my example, in actual code I'm trying to use Foo as protocol, which does not allow fileprivate. But thanks for suggestion, I'll search for other pattern to solve my problem.

Thanks for your example! But at this example I still can create Size directly, without builder. My fault is that I didnt described what I want to achieve - I need every instance created from factory because this way I can easily inject this factory to other code and replace instances, that it return to mocks for testing.
I.e.:
protocol Resizable { func resize(byX: Int) }

class Size : Resizable {
	let x : Int
	func resize(byX: Int) {
		x = x * byX
	}
}

class SizeMock : Resizable {
	func resize(byX: Int) {
		print(resized by "\(byX)")
	}
}

class SizeBuilder {
	var isMock: Bool
	
	func build() -> Resizable {
		isMock ? 
		SizeMock() :
		Size(x: 0, y: o)
	}
}

and then somewhere in client class, SizeBuilder gets injected and used to create instances. And in tests, SizeBuilder.isMock would be set to true.

And I want to ensure during compilation, that no Resizable was created manually in codebase (even by mistake).

Ah, I see. You're using the factory for a different use case than the one I had envisioned. I was thinking about it in the context of wanting to intialize an object, but not having all the fields available at once, such as during the parsing of a file.

Hmm, I'm a little weary of this. If you get high on too many layers of abstraction, you end up in a situation where it's really hard to actually every touch any objects, without really complicated webs fixtures/factories. That's what I like about my "validation is in the initializer" approach, because you get builder-like validation, while still being able to initialize objects directly in a code pad.

What motivates you to want to remove all direct initializations? If the lack of a mock was an issue (e.g. a database call is attempted to be made from a unit test), you would notice it from a test failure, which would remind you to switch it over to a call to an injected factory

Terms of Service

Privacy Policy

Cookie Policy