Czy programowanie obiektowe jest takie samo w każdym języku programowania? Czy jeżeli nauczę się wszystkiego o programowaniu obiektowym to zdobytą teorię będę mógł z łatwością wykorzystać w dowolnym języku?
Zależy co przez to rozumiesz. Jeśli chodzi o idee stojące za OOP i jak pisać dobry kod, to mniej więcej tak. Jeśli jednak sądzisz, że nauczenie jednego języka obiektowego "magicznie" nauczy Cię innego, to nie. Ważną rzeczą jest by pamiętać, że nawet w językach nieobiektowych (jak C) można pisać obiektowo.
Chodzi mi o to czy są znaczące różnice w działaniu pewnych funkcjonalności pomiędzy językami np klasy tworzy się za pomocą słowa class, klasa musi mieć konstruktor itp. A nie że np w jakimś języku nie ma konstruktorów i klasy tworzy się jakoś inaczej. Ciężko jest mi sprecyzować co dokładnie mam na myśli bo bardziej chodzi o ogólne podejście.
Hej,
wiele zależy od języka. Każdy język ma swoją specyfikę programowania obiektowego, o ile w nim da się tak programować. Na przykład w Rubym wszystko jest obiektem. W VBA pod Excelem na przykład wykorzystujemy obiekty zdefiniowane i stworzone przez twórcę Excela (komórki, arkusze, i inne obiekty)...
W Python i Ruby wszystko jest obiektem, w Javie nie do końca.
Idee pewnie i są mniej więcej jednakowe, jednak każdy język czymś się wyróżnia, więc nie wystarczy nauczyć się jednego. Każdy język przede wszystkim ma inną składnię, różnice istnieją też w funkcjonalności klas (np. liczba poziomów dostępu do składowych i sposoby dostępu do nich), ale też jeśli chodzi o takie niuanse jak wielodziedziczenie (jedne języki wspierają, inne nie).
Różnic jest tak dużo, że programowanie obiektowe jest zupełnie inne w każdym języku programowania. Jedne różnią się bardziej, inne mniej, mimo wszystko każdego trzeba się uczyć z osobna.
Programowanie Obiektowe napisał(a):
Chodzi mi o to czy są znaczące różnice w działaniu pewnych funkcjonalności pomiędzy językami np klasy tworzy się za pomocą słowa class, klasa musi mieć konstruktor itp. A nie że np w jakimś języku nie ma konstruktorów i klasy tworzy się jakoś inaczej.
Istnieją takie języki obiektowe, w których klas się nie tworzy, bo ich nawet nie ma.
Przykłady obiektowe w różnych językach:
Java [uruchom]
interface Hello {
void hello();
}
class World implements Hello {
public void hello() { System.out.println("Hello, World!"); }
}
class Greeter implements Hello {
private String name;
Greeter(String name) { this.name = name; }
public void hello() { System.out.println("Hello," + this.name + "!"); }
}
class Main {
public static void main(String[] args) {
Hello world = new World();
Hello greeter = new Greeter("Programowanie Obiektowe");
world.hello();
greeter.hello();
}
}
C [uruchom]
#include <stdio.h>
#define implements struct
typedef struct Hello {
void (*hello)(void*);
} Hello;
struct World {
implements Hello super;
};
void World_hello(void* this) { puts("Hello, World!"); }
Hello* newWorld() {
struct World* world = (struct World*)malloc(sizeof(struct World));
world->super.hello = *World_hello;
return (Hello*)world;
}
struct Greeter {
implements Hello super;
char name[255];
};
void Greeter_hello(void* this) { printf("Hello, %s!\n", ((struct Greeter*)this)->name); }
Hello* newGreeter(char* name) {
struct Greeter* greeter = (struct Greeter*)malloc(sizeof(struct Greeter));
greeter->super.hello = *Greeter_hello;
strncpy(greeter->name, name, 254);
return (Hello*)greeter;
}
void hello(Hello* object) {
object->hello(object);
}
int main() {
Hello* world = newWorld();
Hello* greeter = newGreeter("Programowanie Obiektowe");
hello(world);
hello(greeter);
free(world);
free(greeter);
return 0;
}
Jakby ktoś się zastanawiał kto tak pisze, to zastanówcie się jak jest zaimplementowany dostęp do plików w *niksach, gdzie ten sam interfejs odpowiada za pliki, sockety, pipes, block devices, etc.
Elixir (procesy) [uruchom]
defmodule World do
use GenServer
def start, do: GenServer.start(__MODULE__, nil)
def init(_), do: {:ok, nil}
def handle_call(:hello, _from, state) do
IO.puts("Hello World!")
{:reply, nil, state}
end
end
defmodule Greeter do
use GenServer
def start(name) when is_binary(name), do: GenServer.start(__MODULE__, name)
def init(name), do: {:ok, name}
def handle_call(:hello, _from, state) do
IO.puts("Hello " <> state <> "!")
{:reply, nil, state}
end
end
{:ok, world} = World.start()
{:ok, greeter} = Greeter.start("Programowanie Obiektowe")
GenServer.call(world, :hello)
GenServer.call(greeter, :hello)
Można by tutaj użyć również protokołów, ale IMHO byłoby to oszustwo, bo nie można tego bezpośrednio przełożyć 1:1 na Erlanga. Już behaviours byłyby bliższe, ale dalej "najbardziej obiektowe" rozwiązanie to to.
JS
Ze słowem kluczowym new
(old style) [uruchom]
function World() {}
World.prototype.hello = function () { console.log("Hello World") }
function Greeter(name) { this.name = name }
Greeter.prototype.hello = function () { console.log("Hello " + this.name + "!") }
var world = new World()
var greeter = new Greeter("Programowanie Obiektowe")
world.hello()
greeter.hello()
Przy użyciu domknięć [uruchom]
function World() {
return {
hello: function() { console.log("Hello World!") }
}
}
function Greeter(name) {
return {
hello: function() { console.log("Hello " + name + "!") }
}
}
var world = World()
var greeter = Greeter("Programowanie Obiektowe")
world.hello()
greeter.hello()
Używając nowego class
[uruchom]
class World {
hello() { console.log("Hello, World!") }
}
class Greeter {
constructor(name) { this.name = name }
hello() { console.log(`Hello ${this.name}!`) }
}
const world = new World()
const greeter = new Greeter("Programowanie Obiektowe")
world.hello()
greeter.hello()
Rust [uruchom]
trait Hello {
fn hello(&self);
}
struct World;
impl Hello for World {
fn hello(&self) { println!("Hello World!") }
}
struct Greeter<'a>(&'a str);
impl<'a> Hello for Greeter<'a> {
fn hello(&self) { println!("Hello, {}!", self.0) }
}
fn static_dispatch<T: Hello>(val: T) { val.hello() }
fn dynamic_dispatch(val: Box<dyn Hello>) { val.hello() }
fn main() {
println!("Static dispatch");
static_dispatch(World);
static_dispatch(Greeter("Programowanie Obiektowe"));
println!("\nDynamic dispatch");
dynamic_dispatch(Box::new(World));
dynamic_dispatch(Box::new(Greeter("Programowanie Obiektowe")));
}
Tutaj są 2 rozwiązania, bo Rust tak samo jak C++ pozwala zarówno na statyczny dispatching (w czasie kompilacji jest definiowana wersja, która zostanie odpalona) oraz dynamiczny (w czasie wykonywania programu jest używana vtable
do określenia jaka funkcja zostanie odpalona).
Go [uruchom]
package main
import (
"fmt"
)
type Hello interface {
hello()
}
type World struct{}
func (_ World) hello() {
fmt.Println("Hello, World!")
}
type Greeter struct {
name string
}
func (greeter Greeter) hello() {
fmt.Println("Hello,", greeter.name, "!")
}
func hello(val Hello) {
val.hello()
}
func main() {
hello(World{})
hello(Greeter{name: "Programowanie Obiektowe"})
}
Uzupełnię trochę przedmówcę, jeśli chodzi o przykład w Go, bo nie pojawiło się embedowanie struktów, które "symuluuje" dziedziczenie - raczej się zwykle tego tak nie pisze (a używa interfejsów jak @hauleth pokazał), ale możliwość istnieje.
Tu można uruchomić w Go Playground:
package main
import (
"fmt"
)
type (
// Struktura, która "rozszerza" inne
CarType struct {
name string
wheels int
}
DieselEngine struct {
engineType string
euroNorm int
fuel string
}
EngineType struct {
enginePower int
DieselEngine
}
// Struktura główna
Car struct {
model string
color string
CarType
EngineType
}
)
func main() {
Fiat := &Car{
model: "bravo",
color: "red",
CarType: CarType{
name: "civil",
wheels: 4,
},
EngineType: EngineType{
enginePower: 100,
DieselEngine: DieselEngine{
engineType: "diesel",
euroNorm: 4,
fuel: "oil",
},
},
}
fmt.Println("Model: ", Fiat.model)
fmt.Println("Color: ", Fiat.color)
// Poniższe pola już są "dziedziczone", ale odwołujemy się normalnie
fmt.Println("Type: ", Fiat.name)
fmt.Println("Wheels: ", Fiat.wheels)
fmt.Println("Engine type: ", Fiat.engineType)
fmt.Println("Engine power: ", Fiat.enginePower)
fmt.Println("Engine type: ", Fiat.euroNorm)
fmt.Println("What you need to power the engine: ", Fiat.fuel)
}
hauleth napisał(a):
Przykłady obiektowe w różnych językach:
Java [uruchom]
interface Hello { void hello(); } class World implements Hello { public void hello() { System.out.println("Hello, World!"); } } class Greeter implements Hello { private String name; Greeter(String name) { this.name = name; } public void hello() { System.out.println("Hello," + this.name + "!"); } } class Main { public static void main(String[] args) { Hello world = new World(); Hello greeter = new Greeter("Programowanie Obiektowe"); world.hello(); greeter.hello(); } }
S3 [uruchom]
hello <- function(x, ...) {
UseMethod("hello", x)
}
hello.World <- function(x, ...) {
"Hello, World!"
}
hello.Greeter <- function(x, ...) {
paste0("Hello ", x, " !")
}
world <- "Magdo Madziu Magdaleno"
class(world) <- "World"
greeter <- "Programowanie Obiektowe"
class(greeter) <- "Greeter"
hello(greeter)
hello(world)
S4 [uruchom]
setClass("Greeter", representation(name = "character"))
setClass("World", representation(name = "character")) # muszę dodać name, bo inaczej klasa będzie wirtualną
greeter <- new("Greeter", name = "Programowanie Obiektowe")
world <- new("World")
hello <- function(x) 0
setGeneric("hello")
setMethod("hello", signature("Greeter"), function(x) {
paste0("Hello ", x@name, " !")
})
setMethod("hello", signature("World"), function(x) {
"Hello, World!"
})
hello(world)
hello(greeter)
RC [uruchom]
Przy Reference Class połamałem klawiaturę, więc zrobię kaszanę byle wynik był ok:
Hello <- setRefClass("Hello",
fields = list(name = "character"),
methods = list(hello = function(x) {
paste0("Hello ", name, " !")
}))
World <- Hello$new(name = "World")
Greeter <- Hello$new(name = "Programowanie Obiektowe")
World$hello()
Greeter$hello()