Function to class migration: constructor best practice I am working on moving some complex script logging from functions to classes. In the functional version I would use a string like this

"{header-[cf][0][:12]}_Started: 11:30:21"


{????}_ is the identifying info, here I have a header item header- (which has formatting implications), it will log to both console and file [cf], have no initial indentation [0], and will pad the following string to provide 12 characters before the : that follows Started [:12], so 5 spaces of padding. I had functions to increment the tab value, change the destination (console, file or both), etc. All driven with complicated RegularExpressions to extract the identifying info from the start of the string.
Now, as I move to classes, I have

Enum PxLogType {
    blankLine
    header
    milieuCondition
}

class Px_LogItem {
    [PxLogType]$type
    [bool]$logToConsole
    [bool]$logToFile
    [int]$indent
    [char]$alignToSymbol
    [int]$alignSymbolLocation
    [string]$string

    # Constructor
    Px_LogItem ([PxLogType]$type, [bool]$logToConsole, [bool]$logToFile, [int]$indent, [string]$string) {
        $this.type = $type
        $this.logToConsole = $logToConsole
        $this.logToFile = $logToFile
        $this.indent = $indent
        $this.string = $string
    }
    Px_LogItem ([PxLogType]$type, [bool]$logToConsole, [bool]$logToFile, [int]$indent, [char]$alignToSymbol, [int]$alignSymbolLocation, [string]$string) {
        $this.type = $type
        $this.logToConsole = $logToConsole
        $this.logToFile = $logToFile
        $this.indent = $indent
        $this.alignToSymbol = $alignToSymbol
        $this.alignSymbolLocation = $alignSymbolLocation
        $this.string = $string
    }
}


Ultimately I will have more enumerations, a method to increase the tab indent, etc. This is working, but 

$logItem = [Px_LogItem]::New([PxLogType]::header, $true, $true, 0, ':', 12, "Started: 11:30:21")


is not as readable to my eye. Especially the two instances of $true that replace [cf] to define the target for the log. And depending on where I go, I can see having a few more constructor variations, which starts to get messy. At least, messy in my eyes that are as yet not so familiar with the way constructors are so... specific.

So, I wonder if I am on the right track, and just not familiar enough yet with how class based code looks, or am I off track? I imagine I could do another enum for the log target, with valid values of c, f &amp; cf. Or even have a version that doesn't take those arguments, and defaults both to true to simplify things. That would require method chaining for the constructors, which might actually be a good idea, but how many constructors is considered the threshold for that change? I realize this is all pretty non specific and basically just opinion, but I hope specific enough for some folks to weigh in with their opinions, since I'm too ignorant to have a valid opinion yet.
Having a default value is a good idea.
Also, instead of of using the constructor directly, there is a way to convert from a hash table.
This way only works if the class has a default (no-argument) constructor. (If you define a constructor that takes several arguments, you must also explicitly define a no-argument constructor.)

Enum PxLogType { BlankLine; Header; MilieuCondition }
[Flags()] Enum OutputDestination { Console = 1; File = 2 }

class Alignment {
    [char] $Symbol
    [int] $Location

    [string] ToString() { return "{{Symbol='{0}', Location={1}}}" -f $this.Symbol,$this.Location }
}

class PxLogItem {
    [PxLogType] $Type = 'BlankLine'
    [OutputDestination] $Destination = 'Console'
    [int] $Indent = 0
    [Alignment] $Alignment
    [string] $String = ''
}


You can write as follows.

@(
    New-Object PxLogItem -Property @{ Destination = 'File'; String = 'ABC' }
    [PxLogItem]@{ Indent = 12; String = 'DEF' }
    [PxLogItem]@{ Type = 'Header'; Destination = 'Console,File'; Alignment = @{ Symbol = '#'; Location = 10 }; String = 'GHI' }

) | Format-Table -AutoSize


The result:

     Type   Destination Indent Alignment                 String
     ----   ----------- ------ ---------                 ------
BlankLine          File      0                           ABC   
BlankLine       Console     12                           DEF   
   Header Console, File      0 {Symbol='#', Location=10} GHI


You can also convert strings to objects. This way requires defining a Parse() method.

Enum PxLogType { BlankLine; Header; MilieuCondition }
[Flags()] Enum OutputDestination { Console = 1; File = 2 }

class Alignment {
    [char] $Symbol
    [int] $Location

    [string] ToString() { return "{{Symbol='{0}', Location={1}}}" -f $this.Symbol,$this.Location }
    static [Alignment] Parse($str) { $s,$l = $str.Trim().Split(',', 2); return @{ Symbol = $s; Location = $l } }
}

class PxLogItem {

    [PxLogType] $Type = 'BlankLine'
    [OutputDestination] $Destination = 'Console'
    [int] $Indent = 0
    [Alignment] $Alignment
    [string] $String = ''

    static [PxLogItem] Parse($str) {
        $regex = '^(?:{(?&lt;type&gt;[a-z]+),(?&lt;dest&gt;[cf]+),(?&lt;idt&gt;[0-9]+)(?:,align=(?&lt;align&gt;\S,[0-9]+))?})?(?&lt;text&gt;.*)$'
        if ($str -notmatch $regex) { throw }

        $result = @{ String = $Matches['text'] }
        if ($Matches['type']) {
            $result.Add('Type', $Matches['type'])
            $result.Add('Destination', $Matches['dest'].ToCharArray().ForEach{ "$_" })
            $result.Add('Indent', $Matches['idt'])
        }
        if ($Matches['align']) {
            $result.Add('Alignment', $Matches['align'])
        }
        return $result
    } 
}


All of the following are valid conversions.

@(
    [PxLogItem]@{ Destination = 'Console,File'; Indent = 12; Alignment = '=,15'; String = 'ABC' }
    [PxLogItem]'DEF'
    [PxLogItem]'{m,f,8}GHI'
    [PxLogItem]'{header,cf,4,align=#,10}JKL'

) | Format-Table -AutoSize


The result:

           Type   Destination Indent Alignment                 String
           ----   ----------- ------ ---------                 ------
      BlankLine Console, File     12 {Symbol='=', Location=15} ABC   
      BlankLine       Console      0                           DEF   
MilieuCondition          File      8                           GHI   
         Header Console, File      4 {Symbol='#', Location=10} JKL


Using functions makes them more readable.

function New-PxLogItem {

    Param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $String = '',

        [Parameter(Position=1)]
        [PxLogType]
        $Type = 'BlankLine',

        [Parameter(Position=2)]
        [ValidateRange(0,1000)]
        [int]
        $Indent = 0,

        [Parameter(Position=3)]
        [Alignment]
        $Alignment,

        [switch]
        $Console,

        [switch]
        $File
    )

    if (!$Console -and !$File) { $Console = $true }
    $PSBoundParameters.Add('Destination', !!$Console + 2 * !!$File)
    ('Console','File').ForEach{ [void]$PSBoundParameters.Remove($_) }

    [PxLogItem]$PSBoundParameters
}


$item1 = New-PxLogItem "Hello" Header -indent 4 -align "#,10" -c -f