Table of Contents
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
- [[FLT: 0]]Padrão do construtor na Wikipédia
- Documentação da Apple sobre Construtores de Resultados
- O padrão do construtor em Swift por John Sundell
- Padrão de construção no Guru de Refatorização
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.