当它是正确的一个构造扔一个例外吗?(或者在目标C当它是正确的一init尔返回的零?)

在我看来,一个构造应该失败--并因此拒绝创建一个对象--如果对象是不完整的。即, 构造应该有一个合同与它的呼叫者提供的功能和工作对象在哪些方法可以称为有意义?是否合理?

有帮助吗?

解决方案

构造的工作是使对象变成了可使用状态。基本上有两种学派。

一个集团的主张两个阶段的建筑。构造仅仅是带来的对象入睡状态,其中它拒绝做任何工作。还有一个附加的功能并实际的初始化。

我从来没有理解背后的推理,这种做法。我坚定地在的集团支持的一个阶段的建设,在那里该物体是完全初始化和使用后建设。

一阶段的构造应该扔如果他们不完全的初始化的对象。如果对象不能已经被初始化了,它决不能允许存在,因此构造必须抛。

其他提示

埃里克利珀特说 有4种例外情况。

  • 致命的例外是不是你的错,你无法阻止他们,和你不能理智地清理。
  • 愚蠢的例外是你自己死的错,你可以阻止他们,因此他们是虫子在你的代码。
  • 令人烦恼的例外是结果的不幸的设计的决定。棘手的异常被抛在一个完全非特殊情况,因此必须捕获和处理所有的时间。
  • 最后,外的例外似乎是有点像令人烦恼的例外情况除外,他们是不是结果的不幸的设计选择。相反,他们是杂乱无章的外部现实情况影响到你美丽的、脆程序的逻辑。

你的构造不应该扔致命的异常对其自己的,但是代码,它的执行可能导致致命的例外。像"了记忆"是不是你可以控制的,但如果它发生在一个构造,嘿,它发生。

愚蠢的例外情况不应该发生在任何你的代码,使他们的权利了。

令人烦恼的例外情况(例如是 Int32.Parse())不应该被扔通过构造的,因为他们没有无特殊情况。

最后,外的例外应该是可以避免的,但是如果你做的事你的构造,这取决于外部情况下(如网络或文件系统),这将是适当扔一个例外。

那里是 一般 没有什么可获得了通过离婚对象的初始从建设。RAII是正确的,成功的通话的构造应该导致一个完全初始化的活动对象或应该失败,和 所有 失败在任何时候在任何路径代码应该总是扔一个例外。你一无所获通过使用一个单独的init()方法,除了附加的复杂性在某一水平上。该构造函数合同应当它返回的功能有效的对象或清除之后,本身并抛出。

考虑,如果实现独立的初始方法,您 仍然 有这么称呼的话。它仍然有可能扔的例外情况,他们仍然必须处理的,他们几乎总是要称后,立即构造无论如何,除了现在你有4个可能对象国,而不是2(即,构成,初始化,初始化和失败的vs只是有效的,并不存在).

在任何情况下我们运行在25年的OO发展的情况下,这似乎是一个独立的初始方法将解决一些问题'是设计上的缺陷。如果你不需要象现在那么你不应该建造它的现在,如果你现在就需要它然后你需要它的初始化。亲总是应该的原则之后,随着简单的概念,该行为,国家和API的任何接口应当反映什么样的对象不不它是如何做的,客户代码甚至不应该知道,对象有任何一种内部状态,需要初始化,因而启动后的模式违反了这一原则。

因为所有的麻烦有一部分创建类可能会导致,我想说从来没有。

如果你需要验证的东西在施工期间,作的构造私人和定义的公共静态工厂的方法。该方法可以扔的东西如果是无效的。但是,如果一切检查,它呼吁的构造,这是保证不要扔掉。

一个构造应该把一个异常的时候它无法完成施工所述的对象。

例如,如果该构造应该分配1024KB存,而且它没有这样做,它应该扔一个例外,这种方式的呼叫者的构造知道的对象不是随时可以使用并且存在错误的地方,需要加以固定。

对象是半初始化和半死的正义事业的问题,因为那里真是没办法用于呼叫知道。我宁愿我的构造扔一个错误的当事情是错误的,不必依赖程的运行一个叫给isOK()function其返回真正的或虚假的。

它是总是很狡猾的,尤其是如果你在分配资源的内部构造;根据语言析构不会得到称,因此需要人工清除。这取决于如何,当一个对象的生命周期开始在你的语言。

只有时间我真的做了是当时有一个安全问题的地方,这意味着对象不应该,而不是不能被创建。

这是合理的一个构造扔一个例外,只要它清除本身正确。如果你跟随 RAII 范例(资源的获取是初始化)那么它 很常见的一个构造要做到有意义的工作;写得很好的构造将清理后,如果它不能充分进行初始化。

尽我所知,没有一个是呈现一个相当明显的解决方案,它体现了最佳的两个阶段和两阶段建设。

注: 这个回答假设,而是该原则可适用于大多数语文。

第一,该利益的两个:

一阶段

一阶段建设有利于我们通过防止的对象,从现有的无效状态,从而防止所有各种各样的错误的国家管理和所有的错误来。然而,它留下我们中的一些感觉怪怪的,因为我们不希望我们的构造扔例外情况,有时这就是我们需要做的,当初的论点是无效的。

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

两个阶段的经验证的方法

两阶段建设有利于我们通过允许我们的验证可以执行之外的构造,因此可以防止的必要投掷的例外情况在构造。然而,它给我们留下了"无效"的情况下,这意味着存在的国家,我们必须跟踪和管理的实例,或者我们把它扔掉后,立即堆分配。这引出了一个问题:我们为什么要执行一堆分配,因而存储器的收集,在一个对象我们甚至没有最终使用的?

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

的负面影响的这种方法很少,在我的经验。

一般来说,使用这种方法意味着你不能再使用的类作为也可与检因为反序列化的对象,而不公开的默认构造是很难的,在最好的。然而,如果使用对象为也可与检,你真的不应该被确认对象本身,而是invaliding值的对象,因为你试图使用它们,由于技术上的价值观是不是"无效"方面也可与检.

它还意味着你将最终创造工厂的方法或类当你需要让一个IOC容器,以创建的对象,因为否则的容器不会知道如何化的对象。然而,在很多情况下,该工厂的方法最终被一个 Create 方法本身。

见C++常见问题部分 17.217.4.

在一般情况下,我们发现,代码,是容易的港口,并保持结果的构造是这么写的,他们不失败,和代码,可能会失败是放在一个单独的方法,返回一个错误代码和离开对象在惰性状态。

如果你正在写UI-控制(ASPX,它,WPF,...)应避免投掷例外的构造是因为设计师(Visual Studio)不能处理他们时,它创造了你的控制。知道你控制生命周期的(控制的事件)以及使用延迟的初始化在可能的情况。

注意,如果你把一个异常,在初始化,你就会最终泄漏,如果任何代码用 [[[MyObj alloc] init] autorelease] 模式,因为例外会跳过自动释放.

看到这个问题:

你怎么防止泄漏时提高一个例外在init?

你绝对应该把一个异常从一个构造,如果你无法创建一个有效的对象。这可以让你提供适当的变量在类。

在实践中,可能必须要非常小心。请记住,在C++、析构将不能称之为,所以如果你扔在分配资源,需要采取非常谨慎处理,正确!

这页 有一个彻底的讨论情况在C++。

扔一个例外,如果你无法初始化的对象,在构造,其中一个例子是非法的论点。

作为一般规则的例外应该始终被抛尽快,因为它使得调试更容易时,问题的根源更接近于法令的东西是错误的。

投掷的一个例外在建设是一个好的方法做你的代码的方式更加复杂。事情似乎是简单的突然变得很难。例如,我们说你有一个堆栈。你怎么流行栈和返回前的价值?好吧,如果对象堆能扔在自己的构造(建造的临时返回对话),可以不能保证你不会失去的数据(减堆的指针,建造返回的价值,使用复制构造的价值叠,这将引发的,现在有一堆,只是失去了一个项目)!这就是为什么std::堆::弹不返回的价值,你必须叫std::堆::顶部。

这个问题是很好的描述 在这里,, 检查项目10,编写例外安全的代码。

通常的合同在OO是对象的方法做实际上的功能。

因此,作为一个corrolary,永远不会返回一个僵尸对象形成一个构造/init。

一个僵尸不是功能性和可缺少的内部组成。只是一个空的指针例外等待发生。

我第一次僵尸在目标C中,许多年前。

像所有经验法则,存在一个"例外"。

这完全是可能的, 特定的接口 可能有一项合同说 存在着一个方法的"初始",也就是允许thron一个例外。这一目inplementing这个界面可能不作出正确的反应以任何电话除外财产者,直到初始化已经被称为。我使用这种设备的司机在一OO操作系统在启动过程中,这是可行的。

在一般情况下,你不想僵尸的对象。在语言,如一般用 成为 事情变得有点汽水-巴齐,但是过度使用 成为 是坏的风格了。 成为让的对象变成另一个目的,在原地,因此没有必要对信封的包装(进C++)或战略模式(GOF).

我不能地址的最佳做法目标,但在C++它的罚款,对一个构造扔一个例外。尤其是因为没有其他的方法,以确保一个特殊的条件下遇到建设是报告而不是诉诸调isOK()方法。

功能试图块的功能设计专门用于支持失败的构造员初始化(虽然也可用于常规功能也同样)。这是唯一的方法来修改或丰富的异常的信息会被抛出。但是由于其最初的设计目的(使用造)它不允许的例外情况是吞下了一个空抓()条款。

是的,如果该构造失败,以建立一个内部部分,它可以通过选择它的责任扔(和在某些语言的宣布)的一个 明确的例外 适当注意到在构造的文件。

这并不是唯一的选择:它可以完成的构造和建立一个对象,但有一个方法'isCoherent()'返回假,以便能够信号的一个不连贯的国家(可能最好是在某些情况下,为了避免一场残酷的中断的执行工作流程由于一个例外)
警告:如说,由EricSchaefer在他的评论,这可以带来一些复杂性的单元测试(a扔可以增加 圈复杂度 的功能由于条件触发它)

如果失败,因为它的呼叫者(如空参数,提供呼叫者,其中称构造预计非空参数),构造将把一个未经检查的运行异常。

我不确定任何答复可以是完全的语言无关。一些语文处理的例外情况和存储管理方式不同。

我的工作之下编码标准要求的例外情况从来没有被使用,唯一的错误代码在初始化,因为开发商已被烧毁的语言不当处理异常。语言没有垃圾收集将处理堆叠非常不同,这可能问题不RAII的对象。这是很重要的,虽然工作队决定是一致的,所以他们知道,默认情况下,如果他们需要呼初始化后的构造.所有的方法(包括造)也应该被记录为什么例外,他们可以扔掉,这样的呼叫者知道如何处理它们。

我一般赞成的一个单一阶段施工,因为它们很容易忘记的初始化的对象,但有很多的例外情况。

  • 你的语言的支持的例外情况不是很好。
  • 你有一个紧迫的设计原因仍然使用 newdelete
  • 你的初始化是处理密集型的,并应运行异步的线创建的对象。
  • 你是创造一个DLL,可以扔的例外情况之外,它的接口,以应用程序采用不同的语言。在这种情况下,它可能不那么多的问题不扔的例外情况,但确保他们被捕之前,公众的接口。(你可以抓C++的例外情况,但也有篮球跳过。)
  • 静态的构造(C#)

该运的问题有一个"语言无关的"标记...这个问题无法安全地回答相同的方式,为所有语文/情况。

以下C#例的类层次的抛在B类的构造,跳过立即呼吁类的 IDisposeable.Dispose 在退出主要的 using, 跳过明确的处置类的资源。

例如,如果一级创造了一个 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的角度来看,任何时间初始化构造与非法价值观,它应该扔一个例外。那样它不会构成一个糟糕的状态。

对我来说这是一个有些哲学设计的决定。

这是非常好的实例,这是有效的,只要它们存在,从构造函数时间开始。对于许多非凡的情况下,这可能需要投掷例外的构造函数如果存/资源的分配不能作出。

一些其他的方法是初始化()方法,它涉及的一些问题。其中之一是确保初始化()实际上被称为。

一个变种是使用一个懒惰的方法自动呼init()的第一次访问者/增变得称,但需要任何潜在的呼叫者必须要担心的对象是有效的。(而不是"它的存在,因此它是有效的哲学").

我已经看到各种建议的设计图案来处理这个问题。如能够创建一个初始的对象,通过构造函数,但具有叫init()得到你的手上的立的,初始化的对象,与accesors/设置器.

每个方法都有其跌宕起伏;我已经使用了所有这些成功。如果你不准备对使用对象从即时他们创建的,那么我建议大剂量的断言或例外情况,以确保用户不进行交互之前init().

增编

我写了从C++编程人员的观点。我还假设你正确使用RAII语来处理资源被释放时例外抛出。

我只是学习目标C,所以我真的不能说话的经验,但是我没有读过关于这个苹果的文件。

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

它不仅将告诉您如何处理这个问题你要求,但它没有一个良好的工作的说明。

使用工厂或工厂的方法,为所有对象创造的,可以避免无效的对象,而不扔的例外构造方法。创作方法应该回请求的对象,如果它能够创建一个,或null如果不是这样的。你失去了一点点的灵活性处理的建筑中的错误用户的一类,因为返回null不会告诉你什么是错了对象的创造。但它还避免了增加复杂性的多例外处理程序每次你要求的一个目的,而风险的异常你不应该处理的。

最好的建议我见过的关于例外情况是扔一个例外,如果,只有如果替代是未能满足的一个后的情况或保持不变。

这一建议替换不清楚是主观的决定(它是一个 好想法)用技术,精确的问题的基础上设计出决定(不变并后的条件下)你应该已经作出。

构造只是特定的,但不是特殊情况下,咨询意见。所以问题就是,什么应该不变的类有?倡导一个单独的初始化的方法,可称之为建设后,这表明这类具有两个或者更多 工作模式, , 没有准备 模式后建设和至少一个 准备好了 模式,进入之后的初始化。这是一个额外的复杂性,但可以接受的,如果这类具有多种操作模式。这是很难看到如何并发症是值得的,如果这类否则不会有操作模式。

注意,在推动设置成一个独立的初始化方法不能使你避免的例外情况被抛出。例外情况,你可能会构造已经抛出现在将引发初始化的方法。所有有用的方法类将不得不放弃的例外情况,如果他们被称为一种初始化的对象。

还应注意避免可能的例外情况被扔过你的构造麻烦,并且在许多情况下 不可能的 在许多标准图书馆。这是因为设计师的图书馆认为,投掷例外的构造是一个好主意。特别是,任何试图获取一个不可分享的或有限的资源(例如分配存储器)可能会失败,失败是通常所指出,在面向对象的语言和图书馆投掷的一个例外。

输入不应该做任何"智能"的事情所以投掷的一个例外是没有必要的。使用的一种初始化()或Setup()法,如果要执行一些更复杂的目的设置。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top