Powershell: унаследованные классы, вызывающие пустые конструкторы Parent, даже если переданы объекты - PullRequest
1 голос
/ 12 июля 2019

В powershell 5 я сталкиваюсь со странной проблемой наследования классов.

Я хочу обеспечить, чтобы нам передали объект во время установки, например [class]::new($mailbox_object), и я намеревался сделать это, заставив [class]::new() выдать ошибку, если связанный объект не назначен (скажем, дочерним конструктором) ).

Но powershell вызывает пустых родительских конструкторов ПЕРЕД вызовом дочернего конструктора, которому был передан объект, и я не могу понять, является ли это ошибкой или ожидаемой, и что более важно, как обеспечить, чтобы мы имели получить объект во время создания

Шаблон проектирования говорит: я пытаюсь реализовать то, что я называю шаблоном Unified Interface, который представляет собой шаблон Facade, чтобы упростить / унифицировать взаимодействия с похожими, но по-разному типизированными объектами, где действия для этих объектов выбираются с использованием шаблона Strategy и Стратегия автоматически выбирается Фасадом при его создании (в настоящее время пытается использовать скрытую внутри Фасада невидимую Фабрику)

Пример IRL: попытка создать унифицированный интерфейс для объектов почтовых ящиков / групп Exchange и реализовать функцию MemberOf (для возврата групп, членом которых она является). Но почтовые ящики и группы используют разные команды (несмотря на соответствующую функциональность), а версии 365 и On Premises также используют разные команды (get-unifiedgroup вместо get-distributiongroup), поэтому я пытаюсь скрыть эту сложность за объединенным фасадом для ясности и удобства использования

Я готов изменить свой подход, особенно если есть лучший способ сделать это. Просто имейте в виду, что будут как минимум следующие типы разрозненных объектов, каждый из которых будет нуждаться в собственной реализации .MemberOf (): Interface_Mailbox_365, Interface_Mailbox_OnPremises, Interface_Group_365, Interface_Group_OnPremises, и я могу реализовать Offline и общие версии в конце концов.

MRE ниже, строки с> являются выводом. Поскольку я сузил это до проблемы с созданием интерфейса, я не включил Фасад или Фабрику, но я могу добавить их, если они в конечном итоге понадобятся.

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}

Обратите внимание, что даже несмотря на то, что мы вызвали [Interface_Mailbox_365]::new("Mailbox Standin") powershell, выполнил пустой конструктор прародителя, затем пустой конструктор родителя, прежде чем запустить тот, который мы вызвали.

Если бы они выполнялись в другом порядке, было бы хорошо. Если бы они вызывали родительские конструкторы, которые совпадают с одним и тем же параметром qty и типом, это также было бы хорошо

Но это не так, и я не знаю, как решить это без использования какой-то странной акробатики с фабрикой Singleton, которая кажется чрезмерным количеством микроуправления для чего-то, что должно быть обычной потребностью (требующий входной параметр во время инициализации ) так что я думаю, что я что-то упускаю

1 Ответ

1 голос
/ 12 июля 2019

Спасибо @Mathias R. Jessen за помощь в разработке.Сначала я подумал, что мне нужно отделить Фасад / Фабрику от шаблона, вместо того, чтобы иметь один класс.К сожалению, это означало бы, что я звоню [MailObject_Interface]::Build($object), но не возвращаю [MailObject_Interface] тип.

После некоторых исследований я понял, что Матиас говорит, что это дочерний конструктор, child(object){} подразумевает child(object):base(){}и вы можете переопределить это, явно указав child(object):base(object){}

Сравнив это с дополнительным фрагментом, чтобы проверить, что родитель не вызван напрямую, я смог добиться успеха

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)"

И вотрезультаты теста

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

Также извините за необычный формат в функциях тестирования.Я забыл преобразовать отступ в скобках в нормальный стандарт, я использую нестандартный подход, который я считаю более функциональным, поскольку он создает управляющую логику с пробелами, что делает ее более естественной, следуя периметру, который делают ваши глаза при скимминге

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...