it-swarm-vi.tech

Khi nào thì đúng khi một nhà xây dựng đưa ra một ngoại lệ?

Khi nào thì đúng khi một nhà xây dựng đưa ra một ngoại lệ? (Hoặc trong trường hợp của Mục tiêu C: khi nào thì một init'er trở lại con số không?)

Dường như với tôi rằng một nhà xây dựng nên thất bại - và do đó từ chối tạo một đối tượng - nếu đối tượng không hoàn thành. Tức là, nhà xây dựng nên có một hợp đồng với người gọi của nó để cung cấp một đối tượng chức năng và làm việc trên phương thức nào có thể được gọi là có ý nghĩa? Điều đó có hợp lý không?

197
Mark R Lindsey

Công việc của nhà xây dựng là đưa đối tượng vào trạng thái có thể sử dụng được. Về cơ bản có hai trường phái suy nghĩ về điều này.

Một nhóm ủng hộ xây dựng hai giai đoạn. Hàm tạo chỉ đơn thuần đưa đối tượng vào trạng thái ngủ trong đó nó từ chối thực hiện bất kỳ công việc nào. Có một chức năng bổ sung thực hiện khởi tạo thực tế.

Tôi chưa bao giờ hiểu lý do đằng sau phương pháp này. Tôi chắc chắn trong nhóm hỗ trợ xây dựng một giai đoạn, trong đó đối tượng được khởi tạo hoàn toàn và có thể sử dụng sau khi xây dựng.

Các nhà xây dựng một giai đoạn nên ném nếu họ không khởi tạo hoàn toàn đối tượng. Nếu đối tượng không thể được khởi tạo, nó không được phép tồn tại, do đó, hàm tạo phải ném.

261
Sebastian Redl

Eric Lippert nói có 4 loại ngoại lệ.

  • Trường hợp ngoại lệ không phải là lỗi của bạn, bạn không thể ngăn chặn chúng và bạn không thể dọn sạch chúng một cách hợp lý.
  • Trường hợp ngoại lệ là lỗi nghiêm trọng của riêng bạn, bạn có thể đã ngăn chặn chúng và do đó chúng là lỗi trong mã của bạn.
  • Trường hợp ngoại lệ là kết quả của các quyết định thiết kế không may. Các trường hợp ngoại lệ bị ném trong một tình huống hoàn toàn không đặc biệt, và do đó phải được bắt và xử lý mọi lúc.
  • Và cuối cùng, các ngoại lệ ngoại sinh dường như giống như các ngoại lệ gây phiền nhiễu ngoại trừ việc chúng không phải là kết quả của các lựa chọn thiết kế không may. Thay vào đó, chúng là kết quả của thực tế bên ngoài không gọn gàng dựa trên logic chương trình đẹp, sắc nét của bạn.

Nhà xây dựng của bạn không bao giờ nên tự ném một ngoại lệ gây tử vong, nhưng mã mà nó thực thi có thể gây ra ngoại lệ nghiêm trọng. Một cái gì đó như "hết bộ nhớ" không phải là thứ bạn có thể kiểm soát, nhưng nếu nó xảy ra trong một hàm tạo, thì nó sẽ xảy ra.

Các trường hợp ngoại lệ không bao giờ xảy ra trong bất kỳ mã nào của bạn, vì vậy chúng sẽ được xử lý.

Các trường hợp ngoại lệ (ví dụ là Int32.Parse()) không nên bị các nhà xây dựng ném, bởi vì chúng không có trường hợp không đặc biệt.

Cuối cùng, nên tránh các ngoại lệ ngoại sinh, nhưng nếu bạn đang làm gì đó trong hàm tạo của mình phụ thuộc vào hoàn cảnh bên ngoài (như mạng hoặc hệ thống tệp), thì sẽ phù hợp để ném ngoại lệ.

Liên kết tham khảo: https://bloss.msdn.Microsoft.com/ericlippert/2008/09/10/vexing-exceptions/

56
Jacob Krall

nói chung không có gì đạt được bằng cách ly dị đối tượng khởi tạo từ xây dựng. RAII là chính xác, một cuộc gọi thành công đến hàm tạo sẽ dẫn đến một đối tượng trực tiếp được khởi tạo hoàn toàn hoặc nó sẽ thất bại và ALL thất bại tại bất kỳ điểm nào trong bất kỳ đường dẫn mã nào luôn luôn phải có ngoại lệ. Bạn không thu được gì khi sử dụng phương thức init () riêng ngoại trừ độ phức tạp bổ sung ở một mức nào đó. Hợp đồng ctor phải là nó trả về một đối tượng hợp lệ chức năng hoặc nó tự dọn sạch sau khi ném và ném.

Hãy xem xét, nếu bạn triển khai một phương thức init riêng biệt, bạn still phải gọi nó. Nó vẫn có khả năng đưa ra các ngoại lệ, chúng vẫn phải được xử lý và chúng hầu như luôn phải được gọi ngay sau hàm tạo, ngoại trừ bây giờ bạn có 4 trạng thái đối tượng có thể thay vì 2 (IE, được xây dựng, khởi tạo, chưa khởi tạo, và thất bại so với chỉ hợp lệ và không tồn tại).

Trong mọi trường hợp, tôi đã chạy trong 25 năm OO các trường hợp phát triển trong đó có vẻ như một phương thức init riêng biệt sẽ 'giải quyết một số vấn đề' là các lỗi thiết kế. Nếu bạn không cần một đối tượng NGAY BÂY GIỜ thì bạn không nên xây dựng nó ngay bây giờ và nếu bạn cần nó ngay bây giờ thì bạn cần khởi tạo nó. KISS phải luôn luôn tuân thủ nguyên tắc, cùng với khái niệm đơn giản rằng hành vi, trạng thái và API của bất kỳ giao diện nào sẽ phản ánh CÁI GÌ đối tượng làm gì, không phải CÁCH NÀO, mã khách hàng thậm chí không nên biết rằng đối tượng có bất kỳ loại trạng thái nội bộ nào cần khởi tạo, do đó init sau mẫu vi phạm nguyên tắc này.

30
Alhazred

Bởi vì tất cả những rắc rối mà một lớp được tạo một phần có thể gây ra, tôi nói không bao giờ.

Nếu bạn cần xác thực một cái gì đó trong quá trình xây dựng, hãy đặt công cụ xây dựng riêng tư và xác định phương thức nhà máy tĩnh công khai. Phương pháp có thể ném nếu một cái gì đó không hợp lệ. Nhưng nếu mọi thứ kiểm tra, nó gọi hàm tạo, được đảm bảo không ném.

6
Michael L Perry

Một nhà xây dựng nên ném một ngoại lệ khi nó không thể hoàn thành việc xây dựng đối tượng nói trên.

Ví dụ: nếu hàm tạo được phân bổ ram 1024 KB và không thực hiện được, thì nên đưa ra một ngoại lệ, theo cách này, người gọi của hàm tạo biết rằng đối tượng chưa sẵn sàng để sử dụng và có lỗi một nơi nào đó cần được sửa chữa.

Các đối tượng được khởi tạo một nửa và nửa chết chỉ gây ra vấn đề và vấn đề, vì thực sự không có cách nào để người gọi biết. Tôi muốn các nhà xây dựng của tôi đưa ra một lỗi khi có sự cố xảy ra, hơn là phải dựa vào lập trình để thực hiện cuộc gọi đến hàm isOK () trả về đúng hoặc sai.

5
Denice

Nó luôn luôn tinh ranh, đặc biệt nếu bạn đang phân bổ tài nguyên bên trong một hàm tạo; tùy thuộc vào ngôn ngữ của bạn, hàm hủy sẽ không được gọi, vì vậy bạn cần dọn dẹp thủ công. Nó phụ thuộc vào thời điểm cuộc sống của một đối tượng bắt đầu bằng ngôn ngữ của bạn.

Lần duy nhất tôi thực sự làm điều đó là khi có một vấn đề bảo mật ở đâu đó có nghĩa là đối tượng không nên, thay vì không thể, được tạo ra.

4
blowdart

Thật hợp lý khi một nhà xây dựng đưa ra một ngoại lệ miễn là nó tự dọn dẹp đúng cách. Nếu bạn theo RAII paradigm (Tài nguyên là khởi tạo tài nguyên) thì nó khá phổ biến để một nhà xây dựng có ý nghĩa công việc; một hàm tạo được viết tốt sẽ lần lượt tự dọn sạch nếu nó không thể được khởi tạo hoàn toàn.

4
Matt Dillard

Theo như tôi có thể nói, không ai đang trình bày một giải pháp khá rõ ràng, thể hiện tốt nhất cả việc xây dựng một giai đoạn và hai giai đoạn.

lưu ý : Câu trả lời này giả sử C #, nhưng các nguyên tắc có thể được áp dụng trong hầu hết các ngôn ngữ.

Đầu tiên, lợi ích của cả hai:

Một giai đoạn

Xây dựng một giai đoạn có lợi cho chúng ta bằng cách ngăn chặn các đối tượng tồn tại ở trạng thái không hợp lệ, do đó ngăn chặn tất cả các loại quản lý nhà nước sai lầm và tất cả các lỗi đi kèm với nó. Tuy nhiên, điều đó khiến một số người trong chúng ta cảm thấy kỳ lạ bởi vì chúng ta không muốn các nhà xây dựng của mình đưa ra các ngoại lệ và đôi khi đó là những gì chúng ta cần làm khi các đối số khởi tạo không hợp lệ.

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;
    }
}

Hai giai đoạn thông qua phương thức xác nhận

Xây dựng hai giai đoạn mang lại lợi ích cho chúng tôi bằng cách cho phép xác thực của chúng tôi được thực hiện bên ngoài hàm tạo và do đó ngăn chặn sự cần thiết phải ném ngoại lệ trong hàm tạo. Tuy nhiên, nó để lại cho chúng ta các trường hợp "không hợp lệ", điều đó có nghĩa là chúng ta phải theo dõi và quản lý ví dụ hoặc chúng ta vứt bỏ nó ngay lập tức sau khi phân bổ heap. Nó đặt ra câu hỏi: Tại sao chúng ta thực hiện phân bổ heap, và do đó bộ sưu tập bộ nhớ, trên một đối tượng mà chúng ta thậm chí không sử dụng?

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));
        }
    }
}

Giai đoạn đơn thông qua nhà xây dựng tư nhân

Vì vậy, làm thế nào chúng ta có thể giữ ngoại lệ ra khỏi các nhà xây dựng của mình và ngăn bản thân thực hiện phân bổ heap trên các đối tượng sẽ bị loại bỏ ngay lập tức? Điều này khá cơ bản: chúng tôi làm cho hàm tạo riêng tư và tạo các thể hiện thông qua một phương thức tĩnh được chỉ định để thực hiện khởi tạo và do đó phân bổ heap, chỉ sau xác nhận.

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);
    }
}

Async Single-Stage thông qua constructor riêng

Ngoài các lợi ích ngăn chặn xác thực và phân bổ heap nói trên, phương pháp trước đây cung cấp cho chúng tôi một lợi thế tiện lợi khác: hỗ trợ async. Điều này rất hữu ích khi xử lý xác thực nhiều giai đoạn, chẳng hạn như khi bạn cần truy xuất mã thông báo mang trước khi sử dụng API của mình. Bằng cách này, bạn không kết thúc với ứng dụng khách API "đã đăng xuất" không hợp lệ và thay vào đó, bạn có thể chỉ cần tạo lại ứng dụng khách API nếu bạn nhận được lỗi ủy quyền trong khi thử thực hiện yêu cầu.

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);
        }
    }
}

Theo tôi, nhược điểm của phương pháp này là rất ít.

Nói chung, sử dụng phương thức này có nghĩa là bạn không còn có thể sử dụng lớp làm DTO bởi vì việc giải tuần tự hóa cho một đối tượng mà không có hàm tạo mặc định công khai là khó nhất. Tuy nhiên, nếu bạn đang sử dụng đối tượng làm DTO, bạn thực sự không nên xác thực đối tượng, mà thay vào đó là các giá trị trên đối tượng khi bạn cố gắng sử dụng chúng, vì về mặt kỹ thuật, các giá trị không "không hợp lệ" đến DTO.

Điều đó cũng có nghĩa là bạn sẽ kết thúc việc tạo các phương thức hoặc lớp của nhà máy khi bạn cần cho phép một thùng chứa IOC để tạo đối tượng, vì nếu không thì container sẽ không biết cách khởi tạo đối tượng. Tuy nhiên, trong rất nhiều trường hợp, các phương thức xuất xưởng cuối cùng lại trở thành một trong các phương thức Create.

4
cwharris

Lưu ý rằng nếu bạn ném một ngoại lệ vào trình khởi tạo, bạn sẽ bị rò rỉ nếu có bất kỳ mã nào đang sử dụng mẫu [[[MyObj alloc] init] autorelease], vì ngoại lệ sẽ bỏ qua chế độ tự động.

Xem câu hỏi này:

Làm thế nào để bạn tránh rò rỉ khi đưa ra một ngoại lệ trong init?

3
stevex

Nếu bạn đang viết Điều khiển giao diện người dùng (ASPX, WinForms, WPF, ...), bạn nên tránh ném ngoại lệ vào hàm tạo vì nhà thiết kế (Visual Studio) không thể xử lý chúng khi nó tạo điều khiển của bạn. Biết vòng đời kiểm soát của bạn (các sự kiện kiểm soát) và sử dụng khởi tạo lười biếng bất cứ khi nào có thể.

3
Nick

Xem C++ FAQ phần 17.217.4 .

Nói chung, tôi đã thấy rằng mã dễ dàng chuyển và duy trì kết quả hơn nếu các hàm tạo được viết để chúng không bị lỗi và mã có thể bị lỗi được đặt trong một phương thức riêng biệt trả về mã lỗi và để đối tượng ở trạng thái trơ .

3
moonshadow

Ném một ngoại lệ nếu bạn không thể khởi tạo đối tượng trong hàm tạo, một ví dụ là các đối số bất hợp pháp.

Theo nguyên tắc chung, một ngoại lệ phải luôn được ném càng sớm càng tốt, vì nó giúp việc gỡ lỗi dễ dàng hơn khi nguồn của vấn đề gần với phương thức báo hiệu có gì đó không đúng.

2
user14070

Bạn tuyệt đối nên ném ngoại lệ từ hàm tạo nếu bạn không thể tạo đối tượng hợp lệ. Điều này cho phép bạn cung cấp bất biến thích hợp trong lớp của bạn.

Trong thực tế, bạn có thể phải rất cẩn thận. Hãy nhớ rằng trong C++, hàm hủy sẽ không được gọi, vì vậy nếu bạn ném sau khi phân bổ tài nguyên của mình, bạn cần hết sức cẩn thận để xử lý đúng cách!

Trang này có một cuộc thảo luận kỹ lưỡng về tình huống trong C++.

2
Luke Halliwell

Tôi không thể giải quyết thực tiễn tốt nhất trong Objective-C, nhưng trong C++, thật tốt khi một nhà xây dựng đưa ra một ngoại lệ. Đặc biệt là không có cách nào khác để đảm bảo rằng một điều kiện đặc biệt gặp phải khi xây dựng được báo cáo mà không cần dùng đến phương thức isOK ().

Tính năng khối thử chức năng được thiết kế đặc biệt để hỗ trợ các lỗi trong khởi tạo thành viên của nhà xây dựng (mặc dù nó cũng có thể được sử dụng cho các chức năng thông thường). Đó là cách duy nhất để sửa đổi hoặc làm phong phú thông tin ngoại lệ sẽ bị ném. Nhưng vì mục đích thiết kế ban đầu của nó (sử dụng trong các nhà xây dựng), nó không cho phép ngoại lệ bị nuốt bởi một mệnh đề Catch () trống.

1
mlbrock

Tôi không chắc chắn rằng bất kỳ câu trả lời có thể hoàn toàn không biết ngôn ngữ. Một số ngôn ngữ xử lý các trường hợp ngoại lệ và quản lý bộ nhớ khác nhau.

Tôi đã từng làm việc trước đây theo các tiêu chuẩn mã hóa yêu cầu các ngoại lệ không bao giờ được sử dụng và chỉ các mã lỗi trên các trình khởi tạo, bởi vì các nhà phát triển đã bị đốt cháy bởi ngôn ngữ xử lý các ngoại lệ kém. Các ngôn ngữ không có bộ sưu tập rác sẽ xử lý đống và chồng rất khác nhau, điều này có thể quan trọng đối với các đối tượng không phải RAII. Điều quan trọng là mặc dù một nhóm quyết định nhất quán để họ biết theo mặc định nếu họ cần gọi các trình khởi tạo sau các hàm tạo. Tất cả các phương thức (bao gồm cả các hàm tạo) cũng phải được ghi lại rõ ràng về những trường hợp ngoại lệ mà chúng có thể đưa ra, để người gọi biết cách xử lý chúng.

Tôi thường ủng hộ việc xây dựng một giai đoạn, vì thật dễ quên khởi tạo một đối tượng, nhưng có rất nhiều trường hợp ngoại lệ cho điều đó.

  • Hỗ trợ ngôn ngữ của bạn cho các trường hợp ngoại lệ không phải là rất tốt.
  • Bạn có một lý do thiết kế cấp bách để vẫn sử dụng newdelete
  • Khởi tạo của bạn là bộ xử lý chuyên sâu và nên chạy async với luồng đã tạo đối tượng.
  • Bạn đang tạo một DLL có thể ném ngoại lệ ra ngoài giao diện của nó cho một ứng dụng sử dụng ngôn ngữ khác. Trong trường hợp này, vấn đề không phải là không có ngoại lệ, nhưng đảm bảo rằng chúng được bắt trước giao diện công cộng. (Bạn có thể bắt ngoại lệ C++ trong C #, nhưng có nhiều vòng để nhảy qua.)
  • Các hàm tạo tĩnh (C #)
1
Denise Skidmore

Có, nếu nhà xây dựng không xây dựng một trong các phần bên trong của nó, thì có thể - bằng sự lựa chọn - trách nhiệm của mình là phải ném (và bằng ngôn ngữ nhất định để khai báo) một ngoại lệ rõ ràng , được ghi chú trong tài liệu của nhà xây dựng .

Đây không phải là lựa chọn duy nhất: Nó có thể hoàn thành hàm tạo và xây dựng một đối tượng, nhưng với phương thức 'isCoherent ()' trả về false, để có thể báo hiệu trạng thái không liên tục (trong trường hợp nhất định, có thể thích hợp hơn để tránh sự gián đoạn tàn bạo của quy trình thực hiện do một ngoại lệ)
[.__.] Cảnh báo: như EricSchaefer đã nói trong nhận xét của mình, điều đó có thể mang lại một số phức tạp cho thử nghiệm đơn vị (một cú ném có thể làm tăng độ phức tạp chu kỳ của hàm do điều kiện kích hoạt nó)

Nếu nó không thành công do trình gọi (như một đối số null được cung cấp bởi người gọi, trong đó hàm tạo được gọi mong đợi một đối số không null), dù sao thì hàm tạo sẽ ném ngoại lệ thời gian chạy không được kiểm tra.

1
VonC

Ném một ngoại lệ trong quá trình xây dựng là một cách tuyệt vời để làm cho mã của bạn phức tạp hơn. Những điều có vẻ đơn giản đột nhiên trở nên khó khăn. Ví dụ: giả sử bạn có một ngăn xếp. Làm thế nào để bạn bật ngăn xếp và trả về giá trị hàng đầu? Chà, nếu các đối tượng trong ngăn xếp có thể ném vào các hàm tạo của chúng (xây dựng tạm thời để trả về cho người gọi), bạn không thể đảm bảo rằng bạn sẽ không bị mất dữ liệu (con trỏ ngăn xếp giảm dần, xây dựng giá trị trả về bằng cách sử dụng hàm tạo sao chép giá trị trong ngăn xếp, ném, và bây giờ có một ngăn xếp chỉ mất một mục)! Đây là lý do tại sao std :: stack :: pop không trả về giá trị và bạn phải gọi std :: stack :: top.

Vấn đề này được mô tả tốt tại đây , kiểm tra Mục 10, viết mã an toàn ngoại lệ.

1
Don Neufeld

Hợp đồng thông thường trong OO là các phương thức đối tượng thực sự hoạt động.

Vì vậy, như một hành tinh, không bao giờ trả lại một đối tượng zombie tạo thành một constructor/init.

Một zombie không hoạt động và có thể thiếu các thành phần bên trong. Chỉ là một ngoại lệ con trỏ null đang chờ xảy ra.

Lần đầu tiên tôi tạo ra zombie trong Objective C, nhiều năm trước.

Giống như tất cả các quy tắc của ngón tay cái, có một "ngoại lệ".

Hoàn toàn có thể một giao diện cụ thể có thể có hợp đồng nói rằng tồn tại một phương thức "khởi tạo" được phép để ngoại lệ. Rằng một đối tượng thực hiện giao diện này có thể không phản hồi chính xác cho bất kỳ cuộc gọi nào ngoại trừ setters thuộc tính cho đến khi khởi tạo được gọi. Tôi đã sử dụng điều này cho trình điều khiển thiết bị trong hệ điều hành OO trong quá trình khởi động và nó hoàn toàn khả thi.

Nói chung, bạn không muốn các đối tượng zombie. Trong các ngôn ngữ như Smalltalk với trở thành mọi thứ trở nên hơi ồn ào, nhưng việc lạm dụng trở thành cũng là phong cách xấu. Trở thành cho phép một đối tượng thay đổi thành một đối tượng khác tại chỗ, do đó không cần phải có trình bao bọc (Advanced C++) hoặc mẫu chiến lược (GOF).

1
Tim Williscroft

Câu hỏi của OP có thẻ "bất khả tri ngôn ngữ" ... câu hỏi này không thể được trả lời một cách an toàn theo cùng một cách cho tất cả các ngôn ngữ/tình huống.

Hệ thống phân cấp lớp của ví dụ C # ném vào hàm tạo của lớp B, bỏ qua một cuộc gọi ngay lập tức đến _IDisposeable.Dispose_ khi thoát khỏi using chính, bỏ qua việc xử lý rõ ràng các tài nguyên của lớp A.

Ví dụ, nếu lớp A đã tạo Socket khi xây dựng, được kết nối với tài nguyên mạng, thì đó vẫn có thể là trường hợp sau khối using (một sự bất thường tương đối ẩn).

_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.
    }
}
_
1
Ashley

Tôi chỉ đang học Mục tiêu C, vì vậy tôi thực sự không thể nói từ kinh nghiệm, nhưng tôi đã đọc về điều này trong các tài liệu của Apple.

http://developer.Apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/ch CHƯƠNG_3_section_6.html

Nó không chỉ cho bạn biết làm thế nào để xử lý câu hỏi bạn đã hỏi, mà nó còn làm tốt công việc giải thích nó.

0
Scott Swezey

Lời khuyên tốt nhất tôi từng thấy về các trường hợp ngoại lệ là đưa ra một ngoại lệ nếu và chỉ khi, phương án thay thế là không đáp ứng điều kiện đăng hoặc duy trì bất biến.

Lời khuyên đó thay thế cho một quyết định chủ quan không rõ ràng (có phải là ý kiến ​​hay) bằng một câu hỏi kỹ thuật, chính xác dựa trên các quyết định thiết kế (điều kiện bất biến và bài đăng) mà bạn nên có.

Các nhà xây dựng chỉ là một trường hợp cụ thể, nhưng không đặc biệt, cho lời khuyên đó. Vì vậy, câu hỏi trở thành, một lớp bất biến nên có gì? Những người ủng hộ một phương thức khởi tạo riêng biệt, được gọi sau khi xây dựng, đang gợi ý rằng lớp có hai hoặc nhiều hơn chế độ vận hành, với chế độ chưa có sau khi xây dựng và ít nhất một - sẵn sàng chế độ, được nhập sau khi khởi tạo. Đó là một sự phức tạp bổ sung, nhưng chấp nhận được nếu lớp có nhiều chế độ hoạt động. Thật khó để thấy sự phức tạp đó đáng giá như thế nào nếu lớp học không có chế độ hoạt động.

Lưu ý rằng việc đẩy thiết lập vào một phương thức khởi tạo riêng biệt không cho phép bạn tránh các ngoại lệ bị ném. Các ngoại lệ mà hàm tạo của bạn có thể đã ném bây giờ sẽ bị ném bởi phương thức khởi tạo. Tất cả các phương thức hữu ích của lớp của bạn sẽ phải đưa ra các ngoại lệ nếu chúng được gọi cho một đối tượng chưa được khởi tạo.

Cũng lưu ý rằng việc tránh khả năng các ngoại lệ bị ném bởi nhà xây dựng của bạn là rắc rối và trong nhiều trường hợp không thể trong nhiều thư viện chuẩn. Điều này là do các nhà thiết kế của các thư viện đó tin rằng ném ngoại lệ từ các nhà xây dựng là một ý tưởng tốt. Cụ thể, bất kỳ thao tác nào cố gắng có được tài nguyên không thể chia sẻ hoặc hữu hạn (như cấp phát bộ nhớ) đều có thể thất bại và thất bại đó thường được chỉ định trong các ngôn ngữ và thư viện OO bằng cách ném ngoại lệ.

0
Raedwald

Nói đúng theo quan điểm Java, bất cứ khi nào bạn khởi tạo hàm tạo với các giá trị bất hợp pháp, nó sẽ đưa ra một ngoại lệ. Bằng cách đó, nó không được xây dựng trong một trạng thái xấu.

0
scubabbl

Đối với tôi đó là một quyết định thiết kế có phần triết lý.

Thật tuyệt khi có những trường hợp có giá trị miễn là chúng tồn tại, từ thời gian ctor trở đi. Đối với nhiều trường hợp không cần thiết, điều này có thể yêu cầu ném ngoại lệ từ ctor nếu không thể thực hiện phân bổ bộ nhớ/tài nguyên.

Một số cách tiếp cận khác là phương thức init () đi kèm với một số vấn đề của riêng nó. Một trong số đó là đảm bảo init () thực sự được gọi.

Một biến thể đang sử dụng một cách tiếp cận lười biếng để tự động gọi init () lần đầu tiên một người truy cập/trình biến đổi được gọi, nhưng điều đó đòi hỏi bất kỳ người gọi tiềm năng nào phải lo lắng về việc đối tượng có hợp lệ. (Trái ngược với "nó tồn tại, do đó nó là triết lý hợp lệ").

Tôi cũng đã thấy nhiều mẫu thiết kế được đề xuất để giải quyết vấn đề này. Chẳng hạn như có thể tạo một đối tượng ban đầu thông qua ctor, nhưng phải gọi init () để có được bàn tay của bạn trên một đối tượng được chứa, khởi tạo với accesors/mutators.

Mỗi cách tiếp cận đều có những thăng trầm; Tôi đã sử dụng tất cả những thứ này thành công. Nếu bạn không tạo các đối tượng sẵn sàng sử dụng ngay từ khi chúng được tạo, thì tôi khuyên bạn nên sử dụng một lượng lớn các xác nhận hoặc ngoại lệ để đảm bảo người dùng không tương tác trước init ().

Phụ lục

Tôi đã viết từ một quan điểm lập trình viên C++. Tôi cũng cho rằng bạn đang sử dụng đúng thành ngữ RAII để xử lý các tài nguyên được phát hành khi ném ngoại lệ.

0
nsanders

Sử dụng các nhà máy hoặc phương thức nhà máy cho tất cả việc tạo đối tượng, bạn có thể tránh các đối tượng không hợp lệ mà không ném ngoại lệ từ các nhà xây dựng. Phương thức tạo sẽ trả về đối tượng được yêu cầu nếu nó có thể tạo một hoặc null nếu không. Bạn mất một chút linh hoạt trong việc xử lý các lỗi xây dựng trong người dùng của một lớp, bởi vì trả về null không cho bạn biết điều gì đã sai trong quá trình tạo đối tượng. Nhưng nó cũng tránh thêm sự phức tạp của nhiều trình xử lý ngoại lệ mỗi khi bạn yêu cầu một đối tượng và nguy cơ bắt ngoại lệ mà bạn không nên xử lý.

0
Tegan Mulholland