문제

생성자가 예외를 발생시키는 것이 적절한 때는 언제입니까?(또는 Objective C의 경우:init'er가 nil을 반환하는 것이 언제 적절한가요?)

제가 보기에는 객체가 완전하지 않으면 생성자가 실패하여 객체 생성을 거부해야 하는 것 같습니다.즉, 생성자는 메소드를 의미 있게 호출할 수 있는 기능적이고 작동하는 객체를 제공하기 위해 호출자와 계약을 맺어야 합니까?그게 합리적인가요?

도움이 되었습니까?

해결책

생성자의 임무는 객체를 사용 가능한 상태로 만드는 것입니다.이에 대해서는 기본적으로 두 가지 학파가 있습니다.

한 그룹은 2단계 건설을 선호합니다.생성자는 단지 객체를 어떤 작업도 거부하는 잠자는 상태로 만듭니다.실제 초기화를 수행하는 추가 기능이 있습니다.

나는 이 접근법의 이유를 결코 이해하지 못했습니다.나는 개체가 완전히 초기화되어 생성 후 사용할 수 있는 1단계 생성을 지원하는 그룹에 확고하게 속해 있습니다.

1단계 생성자는 개체를 완전히 초기화하지 못하면 오류를 발생시켜야 합니다.객체를 초기화할 수 없으면 존재하도록 허용해서는 안 되므로 생성자가 예외를 발생시켜야 합니다.

다른 팁

에릭 리퍼트 라고 4가지 종류의 예외가 있습니다.

  • 치명적인 예외는 귀하의 잘못이 아니므로 이를 예방할 수 없으며 현명하게 제거할 수도 없습니다.
  • 골치아픈 예외는 여러분 자신의 잘못입니다. 여러분이 이를 방지할 수 있었으므로 이는 코드의 버그입니다.
  • 짜증나는 예외는 불행한 디자인 결정의 결과입니다.성가신 예외는 전혀 예외가 아닌 상황에서 발생하므로 항상 포착하고 처리해야 합니다.
  • 마지막으로 외생적 예외는 불행한 설계 선택의 결과가 아니라는 점을 제외하면 성가신 예외와 다소 비슷해 보입니다.오히려 그것은 아름답고 선명한 프로그램 논리에 영향을 미치는 어수선한 외부 현실의 결과입니다.

생성자는 자체적으로 치명적인 예외를 발생시켜서는 안 되지만 생성자가 실행하는 코드는 치명적인 예외를 일으킬 수 있습니다."메모리 부족"과 같은 문제는 제어할 수 없지만 생성자에서 발생하면 발생합니다.

뼈머리 예외는 코드에서 절대 발생해서는 안 되므로 즉시 제거됩니다.

성가신 예외(예는 다음과 같습니다. Int32.Parse())은 예외가 아닌 상황이 아니기 때문에 생성자에 의해 던져져서는 안 됩니다.

마지막으로, 외부 예외는 피해야 하지만 외부 상황(예: 네트워크 또는 파일 시스템)에 따라 생성자에서 작업을 수행하는 경우 예외를 발생시키는 것이 적절할 것입니다.

있다 일반적으로 객체 초기화와 생성을 분리해도 얻을 수 있는 것은 없습니다.RAII가 정확합니다. 생성자 호출이 성공하면 라이브 객체가 완전히 초기화되거나 실패해야 합니다. 모두 코드 경로의 어느 지점에서든 오류가 발생하면 항상 예외가 발생해야 합니다.특정 수준에서 추가적인 복잡성을 제외하고 별도의 init() 메서드를 사용하면 아무 것도 얻을 수 없습니다.ctor 계약은 기능적으로 유효한 개체를 반환하거나 자체적으로 정리하고 throw해야 합니다.

별도의 init 메소드를 구현하는 경우 아직 전화해야 해.여전히 예외를 던질 가능성이 있고, 처리해야 하며 사실상 항상 생성자 바로 다음에 호출해야 합니다. 단, 이제 2개가 아닌 4개의 가능한 객체 상태(IE, 생성됨, 초기화됨, 초기화되지 않음, 실패하고 단지 유효하고 존재하지 않는 경우)

어쨌든 나는 25년 동안 별도의 init 메소드가 '일부 문제를 해결'하는 것처럼 보이는 OO 개발 사례를 설계 결함으로 보았습니다.지금 개체가 필요하지 않다면 지금 생성해서는 안 되며, 지금 필요하다면 초기화해야 합니다.모든 인터페이스의 동작, 상태 및 API는 개체가 수행하는 방식이 아니라 개체가 수행하는 작업을 반영해야 한다는 간단한 개념과 함께 KISS를 항상 따라야 하며 클라이언트 코드는 개체에 어떤 종류의 개체가 있는지조차 인식해서는 안 됩니다. 초기화가 필요한 내부 상태이므로 패턴 이후의 초기화는 이 원칙을 위반합니다.

부분적으로 생성된 클래스로 인해 발생할 수 있는 모든 문제 때문에 절대 그렇지 않다고 말하고 싶습니다.

생성 중에 무언가를 검증해야 하는 경우 생성자를 비공개로 만들고 공개 정적 팩토리 메서드를 정의하세요.무언가 잘못된 경우 메서드가 throw될 수 있습니다.그러나 모든 것이 확인되면 생성자를 호출하며 이는 발생하지 않는 것이 보장됩니다.

생성자는 해당 객체의 생성을 완료할 수 없는 경우 예외를 발생시켜야 합니다.

예를 들어, 생성자가 1024KB의 RAM을 할당해야 하는데 이에 실패하는 경우 예외를 발생시켜야 합니다. 이렇게 하면 생성자의 호출자는 객체를 사용할 준비가 되지 않았으며 오류가 있음을 알 수 있습니다. 고쳐야 할 곳.

절반만 초기화되고 절반만 죽은 개체는 호출자가 이를 알 수 있는 방법이 없기 때문에 문제를 일으킬 뿐입니다.true 또는 false를 반환하는 isOK() 함수에 대한 호출을 실행하기 위해 프로그래밍에 의존하는 것보다 문제가 발생할 때 생성자가 오류를 던지도록 하는 것이 좋습니다.

특히 생성자 내부에 리소스를 할당하는 경우 항상 매우 위험합니다.언어에 따라 소멸자가 호출되지 않으므로 수동으로 정리해야 합니다.귀하의 언어에서 객체의 수명이 언제 시작되는지에 따라 다릅니다.

내가 실제로 이 작업을 수행한 유일한 경우는 객체를 생성할 수 없는 것이 아니라 객체를 생성해서는 안 된다는 것을 의미하는 보안 문제가 어딘가에 있었던 경우였습니다.

생성자가 자신을 적절하게 정리하는 한 예외를 발생시키는 것은 합리적입니다.당신이 따라하면 라이 패러다임(자원 획득은 초기화) ~이다 생성자가 의미 있는 작업을 수행하는 것은 매우 일반적입니다.잘 작성된 생성자는 완전히 초기화할 수 없는 경우 자체적으로 정리됩니다.

내가 아는 한, 1단 구조와 2단 구조의 장점을 모두 구현한 상당히 명확한 솔루션을 제시하는 사람은 아무도 없습니다.

메모: 이 답변에서는 C#을 가정하지만 원칙은 대부분의 언어에 적용될 수 있습니다.

첫째, 두 가지 모두의 이점은 다음과 같습니다.

1단

1단계 구성은 객체가 유효하지 않은 상태로 존재하는 것을 방지하여 모든 종류의 잘못된 상태 관리 및 그에 따른 모든 버그를 방지함으로써 우리에게 이점을 줍니다.그러나 생성자가 예외를 발생시키는 것을 원하지 않기 때문에 우리 중 일부는 이상한 느낌을 갖게 되며 때로는 초기화 인수가 유효하지 않을 때 수행해야 하는 작업이기도 합니다.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(dateOfBirth));
        }

        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }
}

검증 방법을 통한 2단계

2단계 구성은 유효성 검사가 생성자 외부에서 실행될 수 있도록 하여 생성자 내에서 예외를 던질 필요가 없다는 점에서 이점을 제공합니다.그러나 "잘못된" 인스턴스가 남게 됩니다. 즉, 인스턴스에 대해 추적하고 관리해야 하는 상태가 있거나 힙 할당 후 즉시 폐기해야 함을 의미합니다.다음과 같은 질문을 던집니다.왜 우리는 결국 사용하지도 않는 객체에 대해 힙 할당과 메모리 수집을 수행하는 걸까요?

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public void Validate()
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(Name));
        }

        if (DateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }
    }
}

개인 생성자를 통한 단일 단계

그렇다면 생성자에서 예외를 방지하고 즉시 폐기될 객체에 대해 힙 할당을 수행하지 않도록 하려면 어떻게 해야 할까요?매우 기본적입니다.생성자를 비공개로 만들고 인스턴스화를 수행하도록 지정된 정적 메서드를 통해 인스턴스를 생성하므로 힙 할당만 수행됩니다. ~ 후에 확인.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    private Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public static Person Create(
        string name,
        DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }

        return new Person(name, dateOfBirth);
    }
}

개인 생성자를 통한 비동기 단일 스테이지

앞서 언급한 검증 및 힙 할당 방지 이점 외에도 이전 방법론은 또 다른 멋진 이점을 제공합니다.비동기 지원.이는 API를 사용하기 전에 전달자 토큰을 검색해야 하는 경우와 같이 다단계 인증을 처리할 때 유용합니다.이렇게 하면 유효하지 않은 "로그아웃" API 클라이언트가 발생하지 않고 대신 요청을 수행하려고 시도하는 동안 인증 오류가 발생하는 경우 간단히 API 클라이언트를 다시 생성할 수 있습니다.

public class RestApiClient
{
    public RestApiClient(HttpClient httpClient)
    {
        this.httpClient = new httpClient;
    }

    public async Task<RestApiClient> Create(string username, string password)
    {
        if (username == null)
        {
            throw new ArgumentNullException(nameof(username));
        }

        if (password == null)
        {
            throw new ArgumentNullException(nameof(password));
        }

        var basicAuthBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
        var basicAuthValue = Convert.ToBase64String(basicAuthBytes);

        var authenticationHttpClient = new HttpClient
        {
            BaseUri = new Uri("https://auth.example.io"),
            DefaultRequestHeaders = {
                Authentication = new AuthenticationHeaderValue("Basic", basicAuthValue)
            }
        };

        using (authenticationHttpClient)
        {
            var response = await httpClient.GetAsync("login");
            var content = response.Content.ReadAsStringAsync();
            var authToken = content;
            var restApiHttpClient = new HttpClient
            {
                BaseUri = new Uri("https://api.example.io"), // notice this differs from the auth uri
                DefaultRequestHeaders = {
                    Authentication = new AuthenticationHeaderValue("Bearer", authToken)
                }
            };

            return new RestApiClient(restApiHttpClient);
        }
    }
}

내 경험상 이 방법의 단점은 거의 없습니다.

일반적으로 이 방법을 사용한다는 것은 공용 기본 생성자 없이 개체로 역직렬화하는 것이 어렵기 때문에 더 이상 클래스를 DTO로 사용할 수 없음을 의미합니다.그러나 개체를 DTO로 사용하는 경우 실제로 개체 자체의 유효성을 검사해서는 안 되며, 개체를 사용하려고 할 때 개체의 값을 무효화해야 합니다. 기술적으로 값은 "유효하지" 않기 때문입니다. DTO에.

이는 또한 IOC 컨테이너가 객체를 생성하도록 허용해야 할 때 결국 팩토리 메소드나 클래스를 생성하게 된다는 것을 의미합니다. 그렇지 않으면 컨테이너가 객체를 인스턴스화하는 방법을 알 수 없기 때문입니다.그러나 많은 경우 팩토리 메소드는 결국 다음 중 하나가 됩니다. Create 방법 자체.

C++ FAQ 섹션을 참조하세요. 17.2 그리고 17.4.

일반적으로 생성자가 실패하지 않도록 작성되고 실패할 수 있는 코드는 오류 코드를 반환하고 객체를 비활성 상태로 두는 별도의 메서드에 배치되는 경우 결과를 이식하고 유지 관리하기가 더 쉬운 코드를 발견했습니다. .

UI 컨트롤(ASPX, WinForms, WPF 등)을 작성하는 경우 디자이너(Visual Studio)가 컨트롤을 만들 때 예외를 처리할 수 없으므로 생성자에서 예외가 발생하지 않도록 해야 합니다.컨트롤 수명주기(컨트롤 이벤트)를 파악하고 가능하면 지연 초기화를 사용하세요.

초기화 프로그램에서 예외를 던지면 코드가 다음을 사용하는 경우 누출이 발생하게 됩니다. [[[MyObj alloc] init] autorelease] 패턴, 예외가 자동 릴리스를 건너뛰기 때문입니다.

이 질문을 참조하세요:

init에서 예외를 발생시킬 때 누출을 어떻게 방지합니까?

유효한 객체를 생성할 수 없는 경우 반드시 생성자에서 예외를 발생시켜야 합니다.이를 통해 클래스에 적절한 불변성을 제공할 수 있습니다.

실제로는 매우 조심해야 할 수도 있습니다.C++에서는 소멸자가 호출되지 않으므로 리소스를 할당한 후 throw하는 경우 이를 올바르게 처리하도록 세심한 주의가 필요합니다!

이 페이지 C++의 상황에 대해 철저히 논의했습니다.

생성자에서 개체를 초기화할 수 없는 경우 예외를 발생시킵니다. 한 가지 예로는 잘못된 인수가 있습니다.

일반적으로 예외는 항상 가능한 한 빨리 발생해야 합니다. 문제의 원인이 무언가 잘못되었음을 알리는 메서드에 가까울 때 디버깅이 더 쉬워지기 때문입니다.

생성 중에 예외를 발생시키는 것은 코드를 더욱 복잡하게 만드는 좋은 방법입니다.간단해 보이던 일이 갑자기 어려워진다.예를 들어 스택이 있다고 가정해 보겠습니다.스택을 어떻게 팝하고 최상위 값을 반환합니까?글쎄, 스택의 객체가 생성자를 던질 수 있는 경우(호출자에게 반환할 임시 생성) 데이터가 손실되지 않을 것이라고 보장할 수 없습니다(스택 포인터 감소, 값의 복사 생성자를 사용하여 반환 값 생성). 던진 스택이 이제 방금 아이템을 잃어버린 스택을 갖게 되었습니다!이것이 std::stack::pop이 값을 반환하지 않는 이유이며, std::stack::top을 호출해야 합니다.

이 문제는 잘 설명되어 있습니다. 여기, 항목 10의 예외 안전 코드 작성을 확인하세요.

OO의 일반적인 계약은 객체 메서드가 실제로 작동한다는 것입니다.

따라서 필연적으로 좀비 개체를 반환하지 않으려면 생성자/초기화를 작성하십시오.

좀비는 작동하지 않으며 내부 구성 요소가 누락되었을 수 있습니다.단지 널 포인터 예외가 발생하기를 기다리고 있습니다.

나는 몇 년 전 Objective C에서 처음으로 좀비를 만들었습니다.

모든 경험 법칙과 마찬가지로 "예외"가 있습니다.

다음이 전적으로 가능합니다. 특정 인터페이스 예외를 제외 할 수있는 "초기화"방법이 존재한다고 말하는 계약이있을 수 있습니다.이 인터페이스를 구현하는 객체는 초기화가 호출될 때까지 속성 설정자를 제외한 모든 호출에 올바르게 응답하지 않을 수 있습니다.부팅 과정에서 OO 운영 체제의 장치 드라이버에 이것을 사용했는데 실행 가능했습니다.

일반적으로 좀비 개체는 원하지 않습니다.Smalltalk와 같은 언어에서는 ~이 되다 상황이 약간 혼란스러워지지만 과도하게 사용하면 ~이 되다 스타일도 나쁘다. Be는 객체가 현장에서 다른 객체로 변경되도록 하므로 봉투 래퍼(고급 C++) 또는 전략 패턴(GOF)이 필요하지 않습니다.

Objective-C에서는 모범 사례를 다룰 수 없지만 C++에서는 생성자가 예외를 발생시키는 것은 괜찮습니다.특히 isOK() 메서드를 호출하지 않고는 생성 시 발생하는 예외 조건이 보고되도록 보장할 수 있는 다른 방법이 없기 때문입니다.

함수 try 블록 기능은 생성자 멤버별 초기화 실패를 지원하기 위해 특별히 설계되었습니다(일반 함수에도 사용될 수 있음).이는 발생될 예외 정보를 수정하거나 강화하는 유일한 방법입니다.그러나 원래 디자인 목적(생성자에서 사용)으로 인해 빈 catch() 절이 예외를 삼키는 것을 허용하지 않습니다.

예, 생성자가 내부 부분 중 하나를 빌드하는 데 실패하면 선택적으로 해당 항목을 던지거나 특정 언어로 선언하는 책임이 있을 수 있습니다. 명시적 예외 , 생성자 문서에 정식으로 명시되어 있습니다.

이것이 유일한 옵션은 아닙니다:생성자를 완료하고 객체를 빌드할 수 있지만 일관되지 않은 상태를 알릴 수 있도록 false를 반환하는 'isCoherent()' 메서드를 사용합니다(어떤 경우에는 잔인한 중단을 피하기 위해 바람직할 수 있음). 예외로 인한 실행 워크플로)
경고:EricSchaefer가 자신의 의견에서 말했듯이 이는 단위 테스트에 약간의 복잡성을 가져올 수 있습니다. 순환적 복잡성 기능을 트리거하는 조건으로 인한 기능)

호출자 때문에 실패하는 경우(예: 호출자가 제공한 null 인수, 호출된 생성자가 null이 아닌 인수를 예상하는 경우) 생성자는 어쨌든 확인되지 않은 런타임 예외를 발생시킵니다.

어떤 대답이 완전히 언어에 구애받지 않을 수 있는지 잘 모르겠습니다.일부 언어는 예외와 메모리 관리를 다르게 처리합니다.

나는 이전에 예외를 사용하지 않고 초기화에 오류 코드만 요구하는 코딩 표준에 따라 작업한 적이 있습니다. 왜냐하면 개발자들은 예외를 제대로 처리하지 못하는 언어로 인해 지쳤기 때문입니다.가비지 수집이 없는 언어는 힙과 스택을 매우 다르게 처리하며 이는 RAII 개체가 아닌 경우 중요할 수 있습니다.하지만 팀이 생성자 다음에 초기화 프로그램을 호출해야 하는지 기본적으로 알 수 있도록 일관성을 유지하기로 결정하는 것이 중요합니다.생성자를 포함한 모든 메서드는 어떤 예외가 발생할 수 있는지에 대해 잘 문서화되어 호출자가 이를 처리하는 방법을 알 수 있도록 해야 합니다.

나는 일반적으로 객체 초기화를 잊어버리기 쉽기 때문에 단일 단계 구성을 선호하지만 여기에는 많은 예외가 있습니다.

  • 예외에 대한 언어 지원이 좋지 않습니다.
  • 계속 사용해야 할 긴급한 디자인 이유가 있습니다. new 그리고 delete
  • 초기화는 프로세서 집약적이며 개체를 생성한 스레드에 대해 비동기식으로 실행되어야 합니다.
  • 다른 언어를 사용하는 응용 프로그램에 대한 인터페이스 외부에서 예외를 발생시킬 수 있는 DLL을 만들고 있습니다.이 경우 예외를 발생시키지 않는 것이 문제가 아니라 공개 인터페이스 전에 예외를 포착하는 것이 중요할 수 있습니다.(C#에서 C++ 예외를 포착할 수 있지만 넘어야 할 난관이 있습니다.)
  • 정적 생성자(C#)

OP의 질문에는 "언어에 구애받지 않는" 태그가 있습니다.이 질문은 모든 언어/상황에 대해 동일한 방식으로 안전하게 대답할 수 없습니다.

다음 C# 예제의 클래스 계층 구조는 클래스 B의 생성자를 발생시키고 클래스 A에 대한 즉각적인 호출을 건너뜁니다. IDisposeable.Dispose 메인 출구에서 나오면 using, 클래스 A 리소스의 명시적 삭제를 건너뜁니다.

예를 들어 클래스 A가 다음을 만들었다면 Socket 구축 시 네트워크 리소스에 연결되어 있으며 이러한 현상은 건설 이후에도 여전히 그럴 가능성이 높습니다. using 블록(상대적으로 숨겨진 변칙).

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("Initialize A's resources.");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose A's resources.");
    }
}

class B : A, IDisposable
{
    public B()
    {
        Console.WriteLine("Initialize B's resources.");
        throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose B's resources.");
        base.Dispose();
    }
}
class C : B, IDisposable
{
    public C()
    {
        Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose C's resources.");
        base.Dispose();
    }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (C c = new C())
            {
            }
        }
        catch
        {           
        }

        // Resource's allocated by c's "A" not explicitly disposed.
    }
}

Java 관점에서 엄격하게 말하면, 잘못된 값으로 생성자를 초기화할 때마다 예외가 발생해야 합니다.이렇게 하면 나쁜 상태로 구축되지 않습니다.

나에게 그것은 다소 철학적인 디자인 결정이다.

ctor 시간부터 존재하는 한 유효한 인스턴스를 갖는 것은 매우 좋습니다.많은 중요한 경우에 메모리/리소스 할당을 할 수 없으면 ctor에서 예외를 발생시켜야 할 수도 있습니다.

다른 접근 방식으로는 자체적으로 몇 가지 문제가 있는 init() 메서드가 있습니다.그 중 하나는 init()이 실제로 호출되도록 하는 것입니다.

변형은 접근자/돌연변이자가 처음 호출될 때 init()를 자동으로 호출하기 위해 지연 접근 방식을 사용하지만, 이를 위해서는 잠재적인 호출자가 객체가 유효한지 걱정해야 합니다.("그것이 존재하므로 유효한 철학이다"와는 반대로).

나는 이 문제를 다루기 위해 제안된 다양한 디자인 패턴도 보았습니다.ctor를 통해 초기 객체를 생성할 수 있지만 액세서/뮤테이터가 포함된 초기화된 객체를 손에 넣기 위해 init()를 호출해야 하는 경우 등이 있습니다.

각 접근 방식에는 장점과 단점이 있습니다.나는 이 모든 것을 성공적으로 사용했습니다.생성되는 즉시 사용할 수 있는 객체를 만들지 않는 경우 사용자가 init() 전에 상호 작용하지 않도록 하기 위해 많은 양의 어설션이나 예외를 권장합니다.

부록

나는 C++ 프로그래머의 관점에서 글을 썼습니다.또한 예외가 발생할 때 해제되는 리소스를 처리하기 위해 RAII 관용구를 올바르게 사용하고 있다고 가정합니다.

저는 Objective C를 배우고 있어서 실제로 경험을 바탕으로 말할 수는 없지만 Apple 문서에서 이에 대해 읽었습니다.

http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_6.html

질문한 질문을 처리하는 방법을 알려줄 뿐만 아니라 질문을 설명하는 데에도 도움이 됩니다.

모든 객체 생성에 팩토리 또는 팩토리 메소드를 사용하면 생성자에서 예외를 발생시키지 않고 유효하지 않은 객체를 방지할 수 있습니다.생성 메소드는 요청된 객체를 생성할 수 있는 경우 해당 객체를 반환해야 하며 그렇지 않은 경우 null을 반환해야 합니다.null을 반환해도 객체 생성 시 무엇이 잘못되었는지 알려주지 않기 때문에 클래스 사용자의 구성 오류를 처리할 때 약간의 유연성이 손실됩니다.그러나 개체를 요청할 때마다 여러 예외 처리기가 추가되는 복잡성과 처리해서는 안 되는 예외를 포착할 위험도 방지됩니다.

예외에 관해 제가 본 최고의 조언은 대안이 사후 조건을 충족하지 못하거나 불변성을 유지하는 것이 실패하는 경우에만 예외를 발생시키는 것입니다.

그 조언은 불분명한 주관적 결정을 대체합니다. 좋은 생각) 이미 결정했어야 하는 설계 결정(불변 및 사후 조건)을 기반으로 하는 기술적이고 정확한 질문이 있습니다.

생성자는 해당 조언에 대한 특별한 사례일 뿐 특별하지는 않습니다.따라서 질문은 클래스가 어떤 불변성을 가져야 합니까?생성 후에 호출되는 별도의 초기화 방법을 옹호하는 사람들은 클래스에 두 개 이상의 초기화 방법이 있어야 한다고 제안합니다. 작동 모드, 와 함께 준비되지 않은 건설 후 모드와 적어도 하나 준비가 된 모드, 초기화 후 진입.이는 추가적인 복잡성이지만 클래스에 여러 작동 모드가 있는 경우 허용됩니다.클래스에 운영 모드가 없다면 그 복잡성이 얼마나 가치가 있는지 알기 어렵습니다.

별도의 초기화 방법으로 설정을 푸시해도 예외가 발생하는 것을 피할 수는 없습니다.생성자가 발생했을 수 있는 예외는 이제 초기화 메서드에 의해 발생됩니다.초기화되지 않은 객체에 대해 호출되는 경우 클래스의 모든 유용한 메서드는 예외를 발생시켜야 합니다.

또한 생성자에 의해 예외가 발생할 가능성을 피하는 것은 번거로운 일이며 많은 경우 불가능한 많은 표준 라이브러리에서.이는 해당 라이브러리의 설계자가 생성자에서 예외를 발생시키는 것이 좋은 생각이라고 믿기 때문입니다.특히, 공유 불가능하거나 유한한 리소스(예: 메모리 할당)를 획득하려는 모든 작업은 실패할 수 있으며 해당 실패는 일반적으로 예외를 발생시켜 OO 언어 및 라이브러리에 표시됩니다.

ctors는 "똑똑한" 작업을 수행하지 않으므로 어쨌든 예외를 던질 필요가 없습니다.좀 더 복잡한 개체 설정을 수행하려면 Init() 또는 Setup() 메서드를 사용하십시오.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top