變異子與訪問子

在電腦科學中，變異子方法（mutator或setter）是一種用於控制變量更改的方法。通常，變異子伴隨著訪問子 （accessor或getter）返回私有成員變量的值。

變異子方法最常用於面向對象的編程封裝，與封裝原則保持一致。根據這個原則，類的成員變量被設為私有以隱藏和保護它們不受其他代碼的影響，並且只能由公共成員函數（變異子方法）修改，該函數將所需的新值作為參數來修改私有成員變量。可以將變異子方法與賦值運算符重載進行比較。

變異子方法也可以在非面向對象的環境中使用。在這種情況下，對要修改的變量的引用與新值一起傳遞給變異子。編譯器無法限制代碼繞過變異子方法並直接更改變量。開發人員有責任確保僅通過變異子方法修改變量，而不是直接修改變量。

在支持它們的程式語言中，屬性提供了一種方便的替代方案，而不放棄封裝的效用。

在下面的示例中，完全實現的 變異子方法還可以驗證輸入數據或採取進一步的操作，例如觸發事件。

## 影響

定義變異子和 訪問子方法或屬性塊的另一種方法是為實例變量提供一些可見性，而不是私有化它或直接從對象外部訪問它。可以使用修改器和訪問器定義更精細的控制訪問權限。例如，將一個參數設為只讀，可以通過定義一個訪問器而不提供修改器。兩種方法的可見性可能不同：訪問器通常是公開的；變異子是保護的、包私有的或內部的。

定義了變異子的塊有機會驗證或預處理傳入的數據。如果保證所有外部訪問都通過變異子，則無法繞過這些步驟。例如，如果日期由單獨的私有 `year`、 `month`和`day`變量表示，則傳入日期由變異子`setDate` 拆分，同時為了一致性，相同的私有實例變量由`setYear`和`setMonth`訪問。在所有情況下，1 - 12 之外的月份值都可以被相同的代碼拒絕。

相反，訪問器允許從內部變量合成有用的數據表示，同時保持它們的結構封裝和對外部模塊不可見。貨幣`getAmount`訪問子可以由一個內部私有的數值變量構建一個字串，其小數位數由參數`currency`定義。

現代程式語言常提供在一行中修改器和訪問器的生成樣板—例如 C# 的 `public string Name { get; set; }`和Ruby的 `attr_accessor :name`。在這些情況下，不會為驗證、預處理或合成等操作創建代碼塊。這些簡化的訪問器仍然保留了對簡單公共實例變量進行封裝的優勢，但常見的是，隨著敏捷軟體開發、軟體維護和需求的變化，對數據的需求變得更加複雜。許多自動修改器和訪問器最終會被單獨的代碼塊取代。在實現的早期自動創建它們的好處是，無論是否添加了更複雜的類，類的公共接口都保持相同，如果添加，則不需要進行大量重構。

由於涉及額外的步驟，訪問器函數的效率可能低於直接獲取或存儲數據欄位，然而，這些函數通常是內聯的，從而消除了函數調用的開銷。

## 例子

### 彙編語言

```nasm
student                   struct
    age         dd        ?
student                   ends
```

```nasm
                     .code
student_get_age       proc      object:DWORD
                      mov       ebx, object
                      mov       eax, student.age[ebx]
                      ret
student_get_age       endp

student_set_age       proc      object:DWORD, age:DWORD
                      mov       ebx, object
                      mov       eax, age
                      mov       student.age[ebx], eax
                      ret
student_set_age       endp
```

### C

文件student.h:

```C
#ifndef _STUDENT_H
#define _STUDENT_H

struct student; /* opaque structure */
typedef struct student student;

student *student_new(int age, char *name);
void student_delete(student *s);

void student_set_age(student *s, int age);
int student_get_age(student *s);
char *student_get_name(student *s);

#endif
```

文件student.c:

```C
#include <stdlib.h>
#include <string.h>
#include "student.h"

struct student {
  int age;
  char *name;
};

student *student_new(int age, char *name) {
  student *s = malloc(sizeof(student));
  s->name = strdup(name);
  s->age = age;
  return s;
}

void student_delete(student *s) {
  free(s->name);
  free(s);
}

void student_set_age(student *s, int age) {
  s->age = age;
}

int student_get_age(student *s) {
  return s->age;
}

char *student_get_name(student *s) {
  return s->name;
}
```

文件 main.c:

```C
#include <stdio.h>
#include "student.h"

int main(void) {
  student *s = student_new(19, "Maurice");
  char *name = student_get_name(s);
  int old_age = student_get_age(s);
  printf("%s's old age = %i\n", name, old_age);
  student_set_age(s, 21);
  int new_age = student_get_age(s);
  printf("%s's new age = %i\n", name, new_age);
  student_delete(s);
  return 0;
}
```

文件 Makefile:

```makefile
all: out.txt; cat $<
out.txt: main; ./$< > $@
main: main.o student.o
main.o student.o: student.h
clean: ;$(RM) *.o out.txt main
```

### C++

文件Student.h:

```cpp
#ifndef STUDENT_H
#define STUDENT_H

#include <string>

class Student {
public:
    Student(const std::string& name);

    const std::string& name() const;
    void name(const std::string& name);

private:
    std::string name_;
};

#endif
```

文件Student.cpp:

```cpp
#include "Student.h"

Student::Student(const std::string& name) : name_(name) {
}

const std::string& Student::name() const {
    return name_;
}

void Student::name(const std::string& name) {
    name_ = name;
}
```

### C#

C#支持屬性編程。

```csharp
public class Student {
    private string name;

    /// <summary>
    /// Gets or sets student's name
    /// </summary>
    public string Name {
        get { return name; }
        set { name = value; }
    }
}
```

從(.NET Framework 3.5開始，可以縮寫為自動屬性：

```csharp
public class Student {
    public string Name { get; set; }
}
```

可以限制只有本類可以私有訪問：

```csharp
public class Student {
    public string Name { get; private set; }
}
```

### Python

下例子使用的類，與一個變量、一個訪問子，一個變異子。

```python
class Student:
    # Initializer
    def __init__(self, name: str) -> None:
        # An instance variable to hold the student's name
        self._name = name

    # Getter method
    @property
    def name(self):
        return self._name

    # Setter method
    @name.setter
    def name(self, new_name):
        self._name = new_name
```

```pycon
>>> bob = Student("Bob")
>>> bob.name 
Bob
>>> bob.name = "Alice"
>>> bob.name 
Alice
>>> bob._name = "Charlie" # bypass the setter
>>> bob._name # bypass the getter
Charlie
```

### Visual Basic.NET

類似於C#, 明確使用`Get` 與 `Set` methods.

```vb
Public Class Student

    Private _name As String

    Public Property Name()
        Get
            Return _name
        End Get
        Set(ByVal value)
            _name = value
        End Set
    End Property

End Class
```

VB.NET 2010, 利用Auto Implemented創建一個屬性而不必使用Get與Set語法。編譯器自動創建一個隱變量，如`_name`對應於屬性名`name`.

```vb
Public Class Student
    Public Property name As String
End Class
```