Powershell:继承的类调用Parent的空构造函数,即使传递对象也是如此 [英] Powershell: Inherited classes calling Parent's empty constructors, even when passed objects

查看:56
本文介绍了Powershell:继承的类调用Parent的空构造函数,即使传递对象也是如此的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在powershell 5中,类遇到了一个奇怪的继承问题。



我要强制我们在安装过程中像<$ c $这样传递一个对象c> [class] :: new($ mailbox_object),而我打算通过引起 [class] :: new()来做到这一点如果未分配关联对象(例如子构造函数),则会引发错误。



但是powershell在调用 empty 父构造函数之前调用传递给该对象的子构造函数,我无法弄清楚这是错误还是预期的错误,更重要的是,如何强制我们在创建时必须给它一个对象



设计模式:我正在尝试实现所谓的统一接口模式,这是一个Facade模式,用于简化/统一与相似但类型不同的对象的交互,其中使用策略模式,并且策略在创建时由Facade自动选择(c紧急通过尝试使用隐藏在立面内的不可见工厂)



IRL示例:尝试为Exchange邮箱/组对象创建统一接口,并实现MemberOf函数(返回它所属的组)。但是邮箱和组使用不同的命令(尽管具有匹配功能),并且365和本地版本也使用不同的命令(get-unifiedgroup而不是get-distributiongroup),因此,我试图将这种复杂性隐藏在统一的Facade中,以确保清晰度和可用性



我愿意改变自己的方法,特别是如果有更好的方法可以做到这一点。请记住,至少会有以下不同类型的对象,每个对象都需要自己实现.MemberOf(): Interface_Mailbox_365 Interface_Mailbox_OnPremises Interface_Group_365 Interface_Group_OnPremises ,我最终可能会实现脱机版本和通用版本。 / p>

下面的MRE,带有>的行是输出。由于我将其范围缩小到创建接口的问题,因此我没有包括Facade或Factory,但是如果最终需要它们,可以添加它们。

  class Interface_MailObject 
{
$ MailObject = Interface_MailObject类-初始
Interface_MailObject(){写警告 Interface_MailObject :: new()MailObject :{$($ this.MailObject)}}
静态[Interface_MailObject] Build($ object)
{
if
($ object -eq Mailbox Standin)
{返回[Interface_Mailbox_365] :: new($ object)}
否则
{throw(我们没有到达这里)}
}
}
类Interface_Mailbox:Interface_MailObject
{
$ MailObject = Interface_Mailbox类-初始
Interface_Mailbox(){写警告 Interface_Mailbox :: new()邮件对象:{$($ this。 MailObject)}}
Interface_Mailbox($ MailObject){$ this.MailObject = Interface_Mailbox类-{$($ MailObject)}}
}
类Interface_Mailbox_365:Interface_Mailbox
{
$ MailObject = Interface_Mailbox_365类-初始
Interface_Mailbox_365(){写警告 Interface_Mailbox_365 :: new()MailObject :{$($ this.MailObject)}}
Interface_Mailbox_365($ MailObject){$ this.MailObject = Interface_Mailbox_365类-{$($ MailObject)}}
[object []] MemberOf (){throw( Interface_Mailbox_365.MemberOf TBD)}
}
[Interface_MailObject] :: new( Mailbox Standin)| tee -va a

>警告:Interface_MailObject :: new()MailObject:{Interface_Mailbox_365类-初始}
>警告:Interface_Mailbox :: new()MailObject:{Interface_Mailbox_365类-初始}
>
> MailObject
> ----------
> Interface_Mailbox_365类-{Mailbox Standin}

请注意,即使我们调用 [Interface_Mailbox_365 ] :: new( Mailbox Standin) powershell在运行我们称为祖父母的空构造函数之前,先执行了祖父母的空构造函数。



如果它们以其他顺序执行,那就没问题了。如果他们调用匹配相同数量qty和type的父构造函数,那也没问题



但是它都不起作用,而且我不知道如何解决这个问题在Singleton工厂中使用一些奇怪的杂技,这似乎是对微不足道的事情,因为这应该是一种常见的需求(初始化期间需要输入参数),所以我想我正在忽略某些东西

解决方案

TL:DR



使用 child(object):base(object ){} 声明构造函数,而不是 child(object){}



感谢@Mathias R. Jessen帮助我解决了问题。
起初我以为我必须将Facade / Factory与Template分离开来,而不是能够使它们属于同一类。不幸的是,这意味着我要调用 [MailObject_Interface] :: Build($ object),而不返回 [MailObject_Interface] 类型。



做完一些研究后,我意识到Mathias所说的是子构造函数 child(object){} 推断为 child(object):base(){} ,您可以通过明确声明 child(object):base(object)来覆盖它} {}



将其与另外一个用于验证父母身份的内容配对后,我就能取得成功

  Class MailObject_Interface 
{
[string] $ MailObject

MailObject_Interface()
{抛出(您必须调用:: Build(`$ object),因为我们会基于邮件对象返回专用类型))}

MailObject_Interface($ object){[MailObject_Interface] :: new ()}#这会触发

以上的错误MailObject_Interface($ object,$ codephrase)
{
Wr ite-Warning正在调用MailObject_Interface :: New($($ object),$($ codephrase)){$($ this.MailObject)}
#该代码短语可确保
#我们要么从我们的一个孩子,
#或任何打电话给我们的人那里知道我们的内部工作原理,并负责确保如果
($ codephrase -eq Shazam !)
{$ this.MailObject = $ object}
否则
{[MailObject_Interface] :: new()}#这会触发
以上的错误}
#我们运行:: Build而不是:: New,因为我们要返回子类型的对象而不是我们自己
static [MailObject_Interface] Build($ object)
{
if
($ object -eq邮箱备用)
{返回[Interface_Mailbox_365] :: new($ object)}
否则
{throw(我们不在这里)}
}
}
类Interface_MailObject_Template :MailObject_Interface
{
Interface_MailObject_Template($ object):基本($ object, Shazam!)){写警告调用Interface_MailObject_Template :: New($($ object)){$($ this。 MailObject)}}
[object []] MemberOf(){throw(。MemberOf将是类型+特定于上下文的)}
}
类Interface_Mailbox:Interface_MailObject_Template
{
Interface_Mailbox($ object):基本($ object){编写警告正在调用Interface_Mailbox :: New($($ object)){$($ this.MailObject)}}}
[object [ ]] MemberOf(){throw( Mailbox.MemberOf将是上下文特定的)}
}
类Interface_Mailbox_365:Interface_Mailbox
{
Interface_Mailbox_365($ object):基本($ object){Write-Warning calling Interface_Mailbox_365 :: New($($ object)){$($ this.MailObject)}}}
[object []] MemberOf(){throw( Interface_Mailbox_365.MemberOf TBD )}
}

#\ /粗糙测试\ /#

#应该成功
函数Test_Correct()
{
尝试
{
[MailObject_Interface] $ a = [MailObject_Interface] :: Build( Mailbox Standin)
return成功($ a)
}
捕获
{返回失败}
}

#应该失败
函数Test_New_WithObject_MissingCodephrase()
{
试试
{
$ a = [MailObject_Interface] :: New( Mailbox Standin)
返回成功:($ a)
}
捕获
{返回失败}
}

#应该失败
函数Test_EmptyBuild()
{
试试
{
$ a = [MailObject_Interface] :: Build()
返回成功:($ a)
}
捕获
{返回失败}
}

#应该失败
函数Test_EmptyNew()
{
试试
{
$ a = [MailObject_Interface] :: New()
return成功:($ a)
}
捕获
{返回失败}
}

$(Test_Correct):`tTest_Correct(应该已经成功)
$(Test_New_WithObject_MissingCodephrase):`tTest_New_WithObject_MissingCodephrase(应该失败)
$(Test_EmptyBuild):`tTest_EmptyBuild(应该失败了)
$(Test_EmptyNew):`tTest_EmptyNew(应该失败)

这是测试结果

 >警告:调用MailObject_Interface :: New(Mailbox Standin,Shazam!){} 
>警告:调用Interface_MailObject_Template :: New(Mailbox Standin){Mailbox Standin}
>警告:调用Interface_Mailbox :: New(Mailbox Standin){Mailbox Standin}
>警告:调用Interface_Mailbox_365 :: New(Mailbox Standin){Mailbox Standin}
>成功(Interface_Mailbox_365):Test_Correct(应该成功)
>失败:Test_New_WithObject_MissingCodephrase(应该失败)
>失败:Test_EmptyBuild(应该失败)
>失败:Test_EmptyNew(应该失败)

对于测试功能中的异常格式也感到抱歉。我忘了将支架的压痕转换为正常的标准,我使用了一种非标准的方法,我发现它具有更多的功能,因为它使用空格来构成控制逻辑,使它更自然地跟随您在略读时的视线范围


In powershell 5 I'm running into a strange inheritance problem with classes.

I want to enforce that we are passed an object during setup like [class]::new($mailbox_object), and I was intending to do this by causing [class]::new() to throw an error if it's associated object isn't assigned (say by a child constructor).

But powershell is calling empty parent constructors BEFORE calling the child constructor that was passed the object and I can't figure out if this is a bug or expected, and more importantly how to enforce that we have to be given an object at creation time

Design pattern speak: I'm trying to implement what I call a Unified Interface pattern, which is a Facade pattern to simplify/unify interactions with similar but differently typed objects, where actions for those objects are selected using a Strategy pattern and the the strategy is chosen automatically by the Facade when created (currently by trying to use an invisible Factory hidden within the Facade)

IRL Example: trying to create a unified interface for Exchange Mailbox/Group objects, and implement a MemberOf function (to return which groups it's a member of). But Mailboxes and Groups use different commands (despite matching functionality) AND 365 and On Premises versions also use different commands (get-unifiedgroup instead of get-distributiongroup) so I'm trying to hide that complexity behind a unified Facade for clarity and usability

I'm open to changing my approach, particularly if there's a better way to do this. Just keep in mind there will be at minimum the following types of disparate objects each of which will need their own implementation of .MemberOf(): Interface_Mailbox_365, Interface_Mailbox_OnPremises, Interface_Group_365, Interface_Group_OnPremises, and I may implement Offline and Generic versions eventually.

MRE below, lines with > are the output. Since I've narrowed it to the an issue with the Interface creation, I've not included the Facade or Factory, but I can add them if they end up being needed.

class Interface_MailObject
{
    $MailObject = "Interface_MailObject class - initial"
    Interface_MailObject(){write-warning "Interface_MailObject::new() MailObject: {$($this.MailObject)}"}
    static [Interface_MailObject] Build($object)
    {
        if
            ($object -eq "Mailbox Standin")
            {return [Interface_Mailbox_365]::new($object)}
        else
            {throw("we don't reach here")}
    }
}
Class Interface_Mailbox : Interface_MailObject
{
    $MailObject = "Interface_Mailbox class - initial"
    Interface_Mailbox () {write-warning "Interface_Mailbox::new() MailObject: {$($this.MailObject)}"}
    Interface_Mailbox ($MailObject) {$this.MailObject = "Interface_Mailbox class - {$($MailObject)}"}
}
Class Interface_Mailbox_365 : Interface_Mailbox
{
    $MailObject = "Interface_Mailbox_365 class - initial"
    Interface_Mailbox_365 () {write-warning "Interface_Mailbox_365::new() MailObject: {$($this.MailObject)}"}
    Interface_Mailbox_365 ($MailObject) {$this.MailObject = "Interface_Mailbox_365 class - {$($MailObject)}"}
    [object[]] MemberOf(){throw("Interface_Mailbox_365.MemberOf TBD")}
}
[Interface_MailObject]::new("Mailbox Standin")|tee -va a

> WARNING: Interface_MailObject::new() MailObject: {Interface_Mailbox_365 class - initial}
> WARNING: Interface_Mailbox::new() MailObject: {Interface_Mailbox_365 class - initial}
> 
> MailObject                                     
> ----------                                     
> Interface_Mailbox_365 class - {Mailbox Standin}

Notice that even though we called [Interface_Mailbox_365]::new("Mailbox Standin") powershell executed the grandparent's empty constructor, then the parent's empty constructor, before running the one we called.

If they executed in the other order, it would be fine. If they called parent constructors that match the same parameter qty and type, that would also be fine

But it's doing neither, and I don't know how to resolve it without using some weird acrobatics with a Singleton factory which seems like an excessive amount of micromanagement for something that should be a common need (requiring an input parameter during initialization) so I'm guessing I'm overlooking something

解决方案

TL:DR

Use child(object):base(object){} to declare the constructor instead of child(object){}

Thanks to @Mathias R. Jessen for helping me work it out. At first I thought I had to decouple the Facade/Factory from the Template, rather than being able to have them be the same class. Unfortunately this would mean I'm calling [MailObject_Interface]::Build($object) but not returning a [MailObject_Interface] type.

After doing some research I realized what Mathias was saying is a child constructor child(object){} is inferred to mean child(object):base(){} and you can override this by explicitly stating child(object):base(object){}

Paring that with an additional piece to verify the parent isn't called directly I was able to achieve success

Class MailObject_Interface
{
    [string] $MailObject

    MailObject_Interface ()
    {throw("You must call ::Build(`$object), because we return specialized types based on the mail object")}

    MailObject_Interface ($object) {[MailObject_Interface]::new()} # this triggers the error above

    MailObject_Interface ($object, $codephrase)
    {
        Write-Warning "calling MailObject_Interface::New($($object), $($codephrase)) {$($this.MailObject)}"
        # the Codephrase ensures 
        # either we're being called from one of our children,
        # or whomever calls us is aware of our internal workings and is taking responsibility for making sure we're handled correctly
        if
            ($codephrase -eq "Shazam!") 
            {$this.MailObject = $object}
        else
            {[MailObject_Interface]::new()} # this triggers the error above
    }
    # We run through ::Build instead of ::New because we want to return a child typed object rather than ourselves
    static [MailObject_Interface] Build($object)
    {
        if
            ($object -eq "Mailbox Standin")
            {return [Interface_Mailbox_365]::new($object)}
        else
            {throw("we don't reach here")}
    }
}
Class Interface_MailObject_Template : MailObject_Interface
{
    Interface_MailObject_Template ($object) : base ($object, "Shazam!") {Write-Warning "calling Interface_MailObject_Template::New($($object)) {$($this.MailObject)}"}
    [object[]] MemberOf(){throw(".MemberOf will be type+context specific")}
}
Class Interface_Mailbox : Interface_MailObject_Template
{
    Interface_Mailbox ($object) : base ($object) {Write-Warning "calling Interface_Mailbox::New($($object)) {$($this.MailObject)}"}
    [object[]] MemberOf(){throw("Mailbox.MemberOf will be context specific")}
}
Class Interface_Mailbox_365 : Interface_Mailbox
{
    Interface_Mailbox_365 ($object) : base ($object) {Write-Warning "calling Interface_Mailbox_365::New($($object)) {$($this.MailObject)}"}
    [object[]] MemberOf(){throw("Interface_Mailbox_365.MemberOf TBD")}
}

#\/ Rough Tests \/#

# should succeed
function Test_Correct()
    {
    Try
        {
        [MailObject_Interface]$a = [MailObject_Interface]::Build("Mailbox Standin")
        return "Succeded ($a)"
        }
    Catch
        {return "Failed"}
    }

# should fail
function Test_New_WithObject_MissingCodephrase()
    {
    Try
        {
        $a = [MailObject_Interface]::New("Mailbox Standin")
        return "Succeded: ($a)"
        }
    Catch
        {return "Failed"}
    }

# should fail
function Test_EmptyBuild()
    {
    Try
        {
        $a = [MailObject_Interface]::Build()
        return "Succeded: ($a)"
        }
    Catch
        {return "Failed"}
    }

# should fail
function Test_EmptyNew()
    {
    Try
        {
        $a = [MailObject_Interface]::New()
        return "Succeded: ($a)"
        }
    Catch
        {return "Failed"}
    }

"$(Test_Correct):`tTest_Correct (should have succeeded)"
"$(Test_New_WithObject_MissingCodephrase):`tTest_New_WithObject_MissingCodephrase (should have failed)"
"$(Test_EmptyBuild):`tTest_EmptyBuild (should have failed)"
"$(Test_EmptyNew):`tTest_EmptyNew (should have failed)"

And here are the test results

> WARNING: calling MailObject_Interface::New(Mailbox Standin, Shazam!) {}
> WARNING: calling Interface_MailObject_Template::New(Mailbox Standin) {Mailbox Standin}
> WARNING: calling Interface_Mailbox::New(Mailbox Standin) {Mailbox Standin}
> WARNING: calling Interface_Mailbox_365::New(Mailbox Standin) {Mailbox Standin}
> Succeded (Interface_Mailbox_365): Test_Correct (should have succeeded)
> Failed:   Test_New_WithObject_MissingCodephrase (should have failed)
> Failed:   Test_EmptyBuild (should have failed)
> Failed:   Test_EmptyNew (should have failed)

Also sorry about the unusual format in the Testing functions. I forgot to convert the bracket indenting to the normal standard, I use a nonstandard approach that I find more functional because it frames control logic with whitespace making it more naturally follow the perimeter walk your eyes do when skimming

这篇关于Powershell:继承的类调用Parent的空构造函数,即使传递对象也是如此的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆