Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kent-h/40e6695d745ccd84fc1f052a7af91974 to your computer and use it in GitHub Desktop.
Save kent-h/40e6695d745ccd84fc1f052a7af91974 to your computer and use it in GitHub Desktop.
Covariance of Interface Method Parameters & Return Values - Advanced Example

Covariance of Interface Method Parameters & Return Values

Advanced Example

For this example, let's say we have three classes of user:

  • Advanced Users
    Users of this interface are granted unrestricted read/write access, with more implementation details being exposed
  • Tenant Users
    Users of this interface are granted restricted read/write access (Ex. a tenant application, or a plugin implementation)
  • Read-Only Users
    Users of this interface are granted read-only access (Ex. A read-only interface to be used for a custom UI/Dashboard/Debugger)

Notice that, for this particular example, Tenant and ReadOnly are effectively sub-interfaces of Advanced.
On the other hand, Tenant and ReadOnly are not interfaces of each other.

Also worth noting: users of a particular interface will always keep that interface's access level, no matter which methods they call.

type AdvancedTable interface{
  AddEntry(value1, value2 string) AdvancedEntry
  GetEntry(uint32) AdvancedEntry
  ID() uint32
}

type TenantTable interface{
  AddEntry(value1, value2 string) TenantEntry
}

type ReadOnlyTable interface{
  GetEntry(uint32) ReadOnlyEntry
  ID() uint32
}

type AdvancedEntry interface{
  GetValue1() string
  SetValue1(string)
  GetValue2() string
  SetValue2(string)
  Table() AdvancedTable
  ID() uint32
}

type TenantEntry interface{
  GetValue1() string
  SetValue1(string)
  GetValue2() string
  SetValue2(string)
  Table() TenantTable
}

type ReadOnlyEntry interface{
  GetValue1() string
  GetValue2() string
  Table() ReadOnlyTable
  ID() uint32
}

Current Implementation

Note:

This is one possible implementation
Another likely alternative would involve renaming conflicting methods.
(i.e. - GetEntry(uint32) ReadOnlyEntry -> GetReadOnlyEntry(uint32) ReadOnlyEntry, etc.)
Such an implementation would reduce portability of the caller's code.

Structs

// implements AdvancedTable
type table struct {
  id      uint32
  entries map[uint32]*entry
}

// implements AdvancedEntry
type entry struct {
  table  *table
  id     uint32
  value1 string
  value2 string
}

// implements ReadOnlyTable
type readOnlyTable struct {
  table *table
}

// implements ReadOnlyEntry
type readOnlyEntry struct {
  entry *entry
}

// implements TenantTable
type tenantTable struct {
  table *table
}

// implements TenantEntry
type tenantEntry struct {
  entry *entry
}

Functions

These must be implemented individually for each interface, because each interface is slightly different

// AdvancedTable implementation

func (t *table) AddEntry(value1, value2 string) AdvancedEntry {
  e := &entry{table:table, id: generateUniqueID(), value1: value1, value2: value2})
  t.entries[e.id] = e
  return e
}
func (t *table) GetEntry(id uint32) AdvancedEntry {
  return t.entries[id]
}
func (t *table) ID() uint32 {
  return t.id
}

// AdvancedEntry implementation

func (e *entry) GetValue1() string {
  return e.value1
}
func (e *entry) SetValue1(str string) {
  e.value1 = str
}
func (e *entry) GetValue2() string {
  return e.value2
}
func (e *entry) SetValue2(str string) {
  e.value2 = str
}
func (e *entry) Table() AdvancedTable {
  return e.table
}
func (e *entry) ID() uint32 {
  return e.entry
}

// TenantTable implementation

func (tt tenantTable) AddEntry(value1, value2 string) TenantEntry {
  e := &entry{table:table, id: generateUniqueID(), value1: value1, value2: value2})
  tt.table.entries[e.id] = e
  return TenantEntry{e}
}
  
// TenantEntry implementation

func (te tenantEntry) GetValue1() string {
  return te.entry.value1
}
func (te tenantEntry) SetValue1(str string) {
  te.entry.value1 = str
}
func (te tenantEntry) GetValue2() string {
  return te.entry.value2
}
func (te tenantEntry) SetValue2(str string) {
  te.entry.value2 = str
}
func (te tenantEntry) Table() TenantTable {
  return tenantTable{te.entry.table}
}

// ReadOnlyTable implementation

func (rot readOnlyTable) GetEntry(id uint32) ReadOnlyEntry {
  return rot.table[id]
}
func (rot readOnlyTable) ID() uint32 {
  return rot.table.id
}

// ReadOnlyEntry implementation

func (roe readOnlyEntry) GetValue1() string {
  return roe.entry.value1
}
func (roe readOnlyEntry) GetValue2() string {
  return roe.entry.value2
}
func (roe readOnlyEntry) Table() ReadOnlyTable {
  return readOnlyTable{roe.entry.table}
}
func (roe readOnlyEntry) ID() uint32 {
  return roe.entry.id
}

Proposed Implementation w/ Covariance of Parameters & Return Values

This is significantly more concise

Structs

// implements AdvancedTable, TenantTable, & ReadOnlyTable
type table struct {
  id      uint32
  entries map[uint32]*entry
}

// implements AdvancedEntry, TenantEntry, & ReadOnlyEntry
type entry struct {
  table  *table
  id     uint32
  value1 string
  value2 string
}

Functions

Single implementation for multiple interfaces

// Advanced-, Tenant-, & ReadOnly-Table implementation

func (t *table) AddEntry(value1, value2 string) *entry {
  e := &entry{table:table, id: generateUniqueID(), value1: value1, value2: value2})
  t.entries[e.id] = e
  return e
}
func (t *table) GetEntry(id uint32) *entry {
  return t.entries[id]
}
func (t *table) ID() uint32 {
  return t.id
}

// Advanced-, Tenant-, & ReadOnly-Entry implementation

func (e *entry) GetValue1() string {
  return e.value1
}
func (e *entry) SetValue1(str string) {
  e.value1 = str
}
func (e *entry) GetValue2() string {
  return e.value2
}
func (e *entry) SetValue2(str string) {
  e.value2 = str
}
func (e *entry) Table() *table {
  return e.table
}
func (e *entry) ID() uint32 {
  return e.id
}

This is significantly shorter, clearer, and still maintains isolation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment