O Padrão do Construtor é um dos padrões de design mais práticos para gerenciar a complexidade durante a criação de objetos. Em Swift, onde a segurança e legibilidade do tipo são valorizadas, o Padrão do Construtor oferece uma maneira limpa e encadeável de construir objetos que requerem muitas opções de configuração, parâmetros opcionais ou interdependências complexas. Este artigo explora o padrão em profundidade, desde a implementação básica até variações avançadas, e fornece exemplos reais que você pode aplicar aos seus próprios projetos.

Entender o padrão do construtor

O Padrão do Construtor separa a construção de um objeto complexo da sua representação final. Em vez de forçar um inicializador maciço com dezenas de parâmetros — muitos dos quais podem ser opcionais ou ter valores padrão — você cria um objeto de construtor separado que coleta a configuração passo a passo. Quando todas as propriedades desejadas estão definidas, você chama um método para produzir o produto final, muitas vezes imutável.

Este padrão é especialmente valioso em Swift quando se trata de:

  • Componentes de interface (vistas, células, camadas) com inúmeras opções de aparência
  • Configurações de solicitação de rede (cabeçalhos, parâmetros, autenticação)
  • Dados ou objetos de modelo de Realm com relações opcionais
  • Objetos de domínio que requerem validação antes da criação

O Padrão do Construtor pertence à família criacional e é frequentemente comparado com o Método de Fábrica e os padrões de Fábrica Abstract. No entanto, os construtores são únicos, pois permitem que o mesmo processo de construção crie diferentes representações – você pode reutilizar o construtor para múltiplas configurações sem alterar sua interface.

Implementação do padrão do construtor em Swift: Exemplo de fundação

Vamos começar com um exemplo Swift concreto. Imagine que você precisa de uma subclasse personalizada com várias propriedades configuráveis. Sem um construtor, você pode acabar com um inicializador longo ou um método de configuração carregado de propriedades. Com o construtor, o código torna-se autodocumentante e expressivo.

Passo 1: Defina o produto

O produto é o objeto que você quer criar. Em Swift, é comum usar um para a semântica de valor e torná-lo imutável após a construção.

struct CustomViewConfig {
 let backgroundColor: UIColor
 let cornerRadius: CGFloat
 let borderWidth: CGFloat
 let borderColor: UIColor
 let shadowOpacity: Float
 let shadowRadius: CGFloat
}

Passo 2: Criar o Construtor

O construtor detém valores padrão para cada propriedade e fornece métodos que os atualizam, retornando normalmente (ou o tipo de construtor) para permitir o encadeamento do método.

class CustomViewConfigBuilder {
 private var backgroundColor: UIColor = .white
 private var cornerRadius: CGFloat = 0.0
 private var borderWidth: CGFloat = 0.0
 private var borderColor: UIColor = .clear
 private var shadowOpacity: Float = 0.0
 private var shadowRadius: CGFloat = 0.0

 @discardableResult
 func withBackgroundColor(_ color: UIColor) -> Self {
 self.backgroundColor = color
 return self
 }

 @discardableResult
 func withCornerRadius(_ radius: CGFloat) -> Self {
 self.cornerRadius = radius
 return self
 }

 @discardableResult
 func withBorder(width: CGFloat, color: UIColor) -> Self {
 self.borderWidth = width
 self.borderColor = color
 return self
 }

 @discardableResult
 func withShadow(opacity: Float, radius: CGFloat) -> Self {
 self.shadowOpacity = opacity
 self.shadowRadius = radius
 return self
 }

 func build() -> CustomViewConfig {
 // optional validation can go here
 return CustomViewConfig(
 backgroundColor: backgroundColor,
 cornerRadius: cornerRadius,
 borderWidth: borderWidth,
 borderColor: borderColor,
 shadowOpacity: shadowOpacity,
 shadowRadius: shadowRadius
 )
 }
}

Observe o uso de – isso permite que os chamadores ignorem o valor de retorno se não precisarem de encadeamento, o que pode ser útil em alguns contextos.

Passo 3: Use o Construtor

let config = CustomViewConfigBuilder()
 .withBackgroundColor(.systemBlue)
 .withCornerRadius(12.0)
 .withBorder(width: 1.5, color: .darkGray)
 .withShadow(opacity: 0.3, radius: 4.0)
 .build()

// Apply the config to a view
let myView = UIView()
myView.backgroundColor = config.backgroundColor
myView.layer.cornerRadius = config.cornerRadius
myView.layer.borderWidth = config.borderWidth
myView.layer.borderColor = config.borderColor.cgColor
myView.layer.shadowOpacity = config.shadowOpacity
myView.layer.shadowRadius = config.shadowRadius

Quando usar o padrão do construtor

O padrão de construtor brilha nos seguintes cenários:

  • Muitos parâmetros opcionais: Se um tipo tem mais de 3-4 opções de configuração, um construtor melhora a legibilidade e reduz os argumentos posicionais propensas a erros.
  • Requisitos de imutabilidade: Você quer que o objeto final seja imutável, mas sua construção requer muitos passos onde o estado intermediário importa.
  • Validação complexa: O método pode validar todas as entradas e erros de lançamento se algo for inválido, impedindo que objetos quebrados sejam criados.
  • APIs de fluxo: Você quer uma interface "fluente" ou "cadeável" que leia como linguagem natural.
  • Preocupações de corte de erros: Quando a lógica de construção é reutilizada em vários lugares em uma base de código, o construtor centraliza essa lógica.

No entanto, os construtores nem sempre são a escolha certa. Para objectos simples com poucas propriedades, um inicializador com valores por omissão é frequentemente suficiente. Para objectos que não necessitem de configuração para além do básico, um construtor adiciona sobrecarga desnecessária.

Variações de Padrão do Construtor

Existem várias variações comuns do padrão de construtor usado em projetos Swift:

1. Construtor Clássico

Como mostrado acima – uma classe separada mantém o estado e retorna um produto. Esta é a mais flexível e permite uma complexa lógica de validação e configuração.

2. Construtor com Estrutura (Tipo Valor)

Uma vez que Swift incentiva tipos de valor, você pode implementar o construtor como uma estrutura. No entanto, o encadeamento de método requer métodos de mutação, o que significa que você precisa marcar funções como ] ou retornar uma nova cópia da estrutura. A última abordagem é mais funcional, mas pode ser menos eficiente para muitas atribuições.

struct CustomViewConfigBuilder {
 private var backgroundColor: UIColor = .white
 // ...

 func withBackgroundColor(_ color: UIColor) -> CustomViewConfigBuilder {
 var copy = self
 copy.backgroundColor = color
 return copy
 }

 func build() -> CustomViewConfig {
 return CustomViewConfig(backgroundColor: backgroundColor, ...)
 }
}

3. Construtor de resultados (Swift 5.4+)

Os construtores de resultados de Swift (também chamados de construtores de funções) fornecem uma sintaxe declarativa que está conceitualmente relacionada com o padrão de construtor. Embora não seja uma substituição direta, os construtores de resultados podem ser usados para construir objetos complexos em um estilo de linguagem específica de domínio (DSL). Por exemplo, o SwiftUI é um construtor de resultados conhecido.

Exemplo: Construtor de resultados personalizados para configuração

@resultBuilder
struct ViewConfigBuilder {
 static func buildBlock(_ components: ViewConfigComponent...) -> [ViewConfigComponent] {
 return components
 }
}

protocol ViewConfigComponent {
 func apply(to builder: CustomViewConfigBuilder)
}

struct BackgroundColorComponent: ViewConfigComponent {
 let color: UIColor
 func apply(to builder: CustomViewConfigBuilder) {
 builder.withBackgroundColor(color)
 }
}

// Usage with @ViewConfigBuilder
let config = ViewConfigBuilder.buildBlock(
 BackgroundColorComponent(color: .red),
 CornerRadiusComponent(radius: 8)
)

Esta abordagem é mais avançada e mais bem reservada para DSLs ou quando você deseja impor uma ordem específica de configurações.

Casos de uso do mundo real no desenvolvimento do iOS

Configuração da Solicitação de Rede

Bibliotecas de rede muitas vezes precisam construir solicitações com muitos parâmetros opcionais: URL, método HTTP, cabeçalhos, corpo, parâmetros de consulta, política de cache, tempo limite, etc. Um construtor simplifica isso.

class APIRequestBuilder {
 private var url: URL
 private var method: String = "GET"
 private var headers: [String: String] = [:]
 private var body: Data?
 private var queryItems: [URLQueryItem] = []

 init(url: URL) {
 self.url = url
 }

 func setMethod(_ method: String) -> Self {
 self.method = method
 return self
 }

 func addHeader(key: String, value: String) -> Self {
 headers[key] = value
 return self
 }

 func setBody(_ data: Data) -> Self {
 self.body = data
 return self
 }

 func addQueryItem(name: String, value: String) -> Self {
 queryItems.append(URLQueryItem(name: name, value: value))
 return self
 }

 func build() -> URLRequest {
 var request = URLRequest(url: url)
 request.httpMethod = method
 request.allHTTPHeaderFields = headers
 request.httpBody = body
 if var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
 components.queryItems = queryItems
 request.url = components.url
 }
 return request
 }
}

// Usage
let request = APIRequestBuilder(url: URL(string: "https://api.example.com/users")!)
 .setMethod("POST")
 .addHeader(key: "Content-Type", value: "application/json")
 .setBody(try! JSONEncoder().encode(userData))
 .build()

Configuração da Entidade de Dados de Base

Os objetos gerenciados por dados de núcleo são notoriamente verbose para criar. Um construtor pode encapsular a pesquisa e atribuição de propriedades .

class UserEntityBuilder {
 private let context: NSManagedObjectContext
 private var name: String = ""
 private var email: String = ""
 private var age: Int = 0

 init(context: NSManagedObjectContext) {
 self.context = context
 }

 func withName(_ name: String) -> Self {
 self.name = name
 return self
 }

 func withEmail(_ email: String) -> Self {
 self.email = email
 return self
 }

 func withAge(_ age: Int) -> Self {
 self.age = age
 return self
 }

 func build() -> User {
 let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: context) as! User
 user.name = name
 user.email = email
 user.age = Int32(age)
 return user
 }
}

Erro no tratamento de Construtores

Às vezes, a criação de objetos deve falhar se a configuração for inválida. O método pode ser lançado, que é uma maneira limpa de impor regras.

struct LoginConfig {
 let username: String
 let password: String
 let serverURL: URL
}

class LoginConfigBuilder {
 private var username: String?
 private var password: String?
 private var serverURL: URL?

 func withUsername(_ username: String) -> Self {
 self.username = username
 return self
 }

 func withPassword(_ password: String) -> Self {
 self.password = password
 return self
 }

 func withServerURL(_ url: URL) -> Self {
 self.serverURL = url
 return self
 }

 func build() throws -> LoginConfig {
 guard let username = username, !username.isEmpty else {
 throw BuilderError.missingUsername
 }
 guard let password = password, password.count >= 8 else {
 throw BuilderError.invalidPassword
 }
 guard let serverURL = serverURL else {
 throw BuilderError.missingServerURL
 }
 return LoginConfig(username: username, password: password, serverURL: serverURL)
 }
}

enum BuilderError: Error {
 case missingUsername
 case invalidPassword
 case missingServerURL
}

// Usage
do {
 let config = try LoginConfigBuilder()
 .withUsername("jdoe")
 .withPassword("secret1234")
 .withServerURL(URL(string: "https://auth.example.com")!)
 .build()
} catch {
 print("Failed to build login config: \(error)")
}

Considerações sobre o desempenho

Os construtores em Swift são normalmente leves, mas há algumas coisas a ter em mente:

  • Memoria em cima: Cada instância de construtor possui uma cópia de todas as propriedades até que seja chamada. Para configurações grandes, considere usar um construtor de struct que cria uma cópia apenas sobre mutação (a abordagem funcional).
  • Method chaining: Cada chamada retorna a mesma instância de construtor (para classes) ou uma nova cópia (para structs). Construtores baseados em classes são bons; construtores de struct podem causar cópias extras, mas o compilador otimiza muitos desses fora.
  • Custo de validação: Se a validação em for cara, considere caching ou diferindo-a, ou fornecendo um método de validação leve que possa ser chamado anteriormente.
  • Use em loops:] Se você precisa criar muitos objetos semelhantes, evite recriar o construtor do zero de cada vez. Em vez disso, reutilize um construtor e redefina seu estado após cada ].

Comparação: Construtor vs Fábrica vs Inicializador Direto

Approach Best For Downside
Direct Initializer Simple objects with few required parameters Becomes unreadable with many optional parameters
Factory Method Subclass selection or logic-based creation Does not handle step-by-step configuration
Builder Pattern Complex, configurable, and potentially immutable objects More boilerplate; not suitable for trivial objects

O construtor é complementar às fábricas. Você poderia ter uma fábrica que retorna um construtor pré-configurado, em seguida, deixe o chamador personalizá-lo ainda mais.

Integração com SwiftUI e Combine

Os construtores são um ajuste natural para o estilo declarativo da SwiftUI. Você pode criar um construtor que construa um baseado na configuração.

struct CardViewConfig {
 let title: String
 let subtitle: String
 let iconName: String
 let backgroundColor: Color
 let tapAction: () -> Void
}

class CardViewConfigBuilder {
 private var title: String = ""
 private var subtitle: String = ""
 private var iconName: String = "star"
 private var backgroundColor: Color = .white
 private var tapAction: (() -> Void)? = nil

 func withTitle(_ title: String) -> Self {
 self.title = title
 return self
 }

 func withSubtitle(_ subtitle: String) -> Self {
 self.subtitle = subtitle
 return self
 }

 func withIcon(_ name: String) -> Self {
 self.iconName = name
 return self
 }

 func withBackground(_ color: Color) -> Self {
 self.backgroundColor = color
 return self
 }

 func withTapAction(_ action: @escaping () -> Void) -> Self {
 self.tapAction = action
 return self
 }

 func build() -> CardViewConfig {
 return CardViewConfig(
 title: title,
 subtitle: subtitle,
 iconName: iconName,
 backgroundColor: backgroundColor,
 tapAction: tapAction ?? {}
 )
 }
}

// Usage in a SwiftUI view
struct ContentView: View {
 var body: some View {
 let config = CardViewConfigBuilder()
 .withTitle("Welcome")
 .withSubtitle("Get started with our app")
 .withIcon("hand.wave")
 .withBackground(.blue.opacity(0.1))
 .withTapAction { print("Tapped!") }
 .build()

 CardView(config: config)
 }
}

struct CardView: View {
 let config: CardViewConfig

 var body: some View {
 VStack {
 Image(systemName: config.iconName)
 .font(.largeTitle)
 Text(config.title)
 .font(.headline)
 Text(config.subtitle)
 .font(.subheadline)
 }
 .padding()
 .background(config.backgroundColor)
 .cornerRadius(10)
 .onTapGesture(perform: config.tapAction)
 }
}

Pistas comuns e como evitá - las

  • Esquecendo-se de retornar a si mesmo: Certifique-se de que cada método de setter retorna o tipo de construtor. Use se você quiser permitir que os chamadores ignorem o retorno.
  • Estado mutável compartilhado: Se o seu construtor for usado através de threads, adicione segurança de thread (por exemplo, use uma fila serial privada ou semântica de cópia-em-escrita).
  • Engenharia excessiva: Não aplique o padrão de construtor a cada objeto. Se seu objeto tem apenas duas ou três propriedades, um inicializador simples com valores padrão é mais claro e requer menos código.
  • Falta a validação: Os construtores que não validam em podem produzir objetos em um estado inválido. Sempre verifique suposições no ponto seguro mais cedo.
  • Nomeação de método inconsistente: Use um prefixo consistente como ou para tornar a API reconhecível. Algumas equipes preferem , , etc.

Recursos externos

Conclusão

O padrão do construtor é uma ferramenta robusta em qualquer arsenal de desenvolvedor Swift para lidar com a inicialização de objetos complexos com clareza, manutenção e segurança. Ao separar a lógica de construção do produto final, você pode criar APIs expressivas que são fáceis de usar e difíceis de usar. Se você está configurando visualizações, construindo solicitações de rede ou construindo modelos de domínio, o padrão do construtor ajuda a manter seu código limpo e seus objetos válidos. Comece com o construtor clássico baseado em classe, e depois explore construtores de tipo de valor ou construtores de resultados conforme suas necessidades crescem. Com a prática, você encontrará o equilíbrio certo entre granularidade e simplicidade, tornando seu código Swift mais legível e profissional.