mercredi 17 juillet 2019

powershell access sibling method

I'm having trouble accessing sibling methods in powershell

$group.search.has_member(search) is the same once it gets the members, but $group.retrieve.Members() gets the members differently for 365 groups vs local groups

So I'm trying to have .search.has_member(search) call .retrieve.Members() to minimize code duplication (IRL there's around 15 different versions some with unique retrieval methods, some with shared approaches). But I'm having trouble accessing the sibling's methods

My main goal is I'm trying to build an interface facade that will unify how you interact with Exchange mail objects. 365 mailboxes, local mailboxes, 365 groups, and local groups (among others) all can have Members, MemberOf, Email Addresses and Distinguished Name, but while DN is a property on all objects, Email addresses property is formatted differently on 365 vs local groups (which affects .MatchesSMTP($search)), and retrieving Members() is different for 365 groups vs local groups and should return null for mailboxes and mail contacts regardless of whether it's 365 or local, and retrieval of MemberOf is unique for each object type.

This leads to a significant level of complexity. Originally I was trying to break them out based using inheritance based first on Type(mailbox vs group) then on Server(mailbox_365, mailbox_local, etc), but I ended up with too much duplicated code. Same issue when swapping the order (365_mailbox, 365_group, etc).

So now I'm trying to implement abstractions based on behaviors to better share code when possible, and select style/strategy separately for versions that need a more unique approach. But the .Searches are having trouble accessing the .Retrievals.

I guess I could pass the retrievals as a parameter to the searches constructor like I am for config, but that approach doesn't scale well if I start ending up with more behavior categories with interlaced dependencies. Also if I can get the sibling access working I can stop passing config as a parameter and just access them directly, which should clean up the code a bit more.

Below is a reduced example for reference (yes, despite it's complexity it's very reduced, the final version has around 26 classes last time I counted)

Class Mail_Factory
    {
    static [Mail_Interface] BuildExample1()
        {
        $mail_object = "Pretend 365 group"
        return [Mail_Factory]::Build($mail_object)
        }
    static [Mail_Interface] BuildExample2()
        {
        $mail_object = "Pretend Local Group"
        return [Mail_Factory]::Build($mail_object)
        }
    static [Mail_Interface] Build($mail_object)
        {
        [Mail_Interface] $interface = [Mail_Interface]::new()
        $interface.config = [Mail_Config]::new($mail_object)
        $interface.retrieve = [Mail_Retrieve]::new($interface.config)
        $interface.search = [Mail_Search]::new($interface.config)

        [Mail_Retrieve_Members_Style] $members_style = switch ($mail_object)
            {
            ("Pretend 365 group") { [Mail_Retrieve_Members_365Group]::new($interface.config) }
            ("Pretend Local Group") { [Mail_Retrieve_Members_LocalGroup]::new($interface.config) }
            }
        # notice that we're setting a specific retreival strategy for "members" depending on object type
        $interface.config.members_style = $members_style

        return $interface
        }
    }

Class Mail_Interface
    {
    Mail_Interface(){}

    [Mail_Config] $config
    [Mail_Retrieve] $retrieve # This is a facade to unify the way we call different kinds of retreivals (directly from settings, derived from settings, shared based on type, or unique to a specific combination of settings)
    [Mail_Search] $search     # This is a facade for the same reasons as $retreive
    [bool] has_member($search) {return $this.search.has_member($search)}
    [object] members() {return @($this.retrieve.members())}
    }

Class Mail_Config
    {
    Mail_Config($mail_object) {$this.mail_object = $mail_object}
    [Mail_Retrieve_Members_Style] $members_style # set by factory
    [object] $mail_object
    }

Class Mail_Retrieve
    {
    Mail_Retrieve($config){$this.config = $config}
    [Mail_Config] $config
    [object] Members(){return $this.config.members_style.Members()}
    }

Class Mail_Retrieve_Members_Style
    {
    Mail_Retrieve_Members_Style($config){$this.config = $config}

    [Mail_Config] $config
    [object] Members(){throw("Syle not yet selected")}
    }

Class Mail_Retrieve_Members_365Group : Mail_Retrieve_Members_Style
    {
    Mail_Retrieve_Members_365Group($config) : base($config) {}

    # inherited: [Mail_Config] $config
    [object] Members(){return "member of 365 group"}
    }

Class Mail_Retrieve_Members_LocalGroup : Mail_Retrieve_Members_Style
    {
    Mail_Retrieve_Members_LocalGroup($config) : base($config) {}

    # inherited: [Mail_Config] $config
    [object] Members(){return "member of local group"} 
    }

Class Mail_Search
    {
    Mail_Search($config){$this.config = $config}

    [Mail_Config] $config
    [bool] has_member($search)
        {
        $members = $this.parent.retrieve.members() # !!! Problem exists here !!!
        if ($members -contains "$search") {return $true}
        else {return $false}
        }
    }

function TestExample1()
    {
    # from
    #   $a.search.has_member()
    # we're trying to access 
    #   $a.retrieve.Members()

    $a = [Mail_Factory]::BuildExample1()
    $member_a = $a.members()[0]
    if ($a.has_member($member_a))
        {"Success!"}
    else
        {"Failed, member match incorrect"}
    }


function TestExample2()
    {
    # from
    #   $b.search.has_member()
    # we're trying to access 
    #   $b.retrieve.Members()

    $b = [Mail_Factory]::BuildExample2()
    $member_b = $b.members()[0]
    $b.has_member($member_b)
    }

$result_a = TestExample1
$result_b = TestExample2

$result_a
$result_b

This spits out the following error (I've marked the line with !!! Problem exists here !!!)

You cannot call a method on a null-valued expression.
At line:88 char:9
+         $members = $this.super.retrieve.members() # !!! Problem exist ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

I have full control over this project and currently I'm open to completely refactoring to a different approach if I'm going about this the wrong way. I'm comfortable using chained constructors especially if they improve readability. And I've been exploring design patterns on G4G, but my experience with them is still neophytic

Aucun commentaire:

Enregistrer un commentaire