Чаму мы перадаем аб'екты, а не член аб'екта на функцыю?

У мяне ёсць метад Foo() у класе А , які прымае ў якасці ўваходных аргументаў з дадзеных членаў класа B сярод іншых рэчаў (скажам, яны з'яўляюцца два паплаўка і адзін INT). Як я разумею, гэта, як правіла, лепш, каб гэта рэалізаваць нешта накшталт:

A->FOO1(B, other_data_x)

а не

A->FOO2(B.member1, B.member2, B.member3, other_data_x).

Я збіраю адзін, але не адзіны, перавага гэтага складаецца ў тым, што яна пакідае дэталі з якіх члены B , каб атрымаць доступ да Foo1() і так дапамагае схаваць дэталі рэалізацыі ,

But what I wonder about is whether this actually introduces additional coupling between classes A and B. Class A in the former case has to know class B exists (via something like include class_B_header.h), and if the members of B change or are moved to a different class or class B is eliminated altogether, you have to modify A and FOO1() accordingly. By contrast, in the latter approach, FOO2() doesn't care whether class B exists, in fact all it cares about is that it is supplied with two floats (which in this case consist of B.member1 and B.member2) and an int (B.member3). There is, to be sure, coupling in the latter example as well, but this coupling is handled by wherever FOO2() gets called or whatever class happens to be calling FOO2(), а не in the definition of A or B.

Я мяркую, што другая частка гэтага пытання, ці ёсць добры спосаб аддзяліць А і B далей, калі мы хочам рэалізаваць рашэнне, як Foo1() ?

12
Праходзіце значэння не класы.
дададзена аўтар Captain Obvlious, крыніца

8 адказы

<�Р> Але што цікава, пра тое, ці з'яўляецца на самай справе ўяўляе гэты дадатковы   Сувязь паміж класам А і В. </р>

Так, гэта робіць. <�Код> А і У цяпер цесна звязаныя.

Вы, здаецца, пад уражаннем, што гэта, як правіла, прынята, што адзін павінен перадаваць аб'екты, а не членаў гэтых аб'ектаў. Я не ведаю, як вы атрымалі гэтае ўражанне, але гэта не так. Калі вы павінны адправіць аб'ект або члены гэтага аб'екта цалкам залежыць ад таго, што вы спрабуеце зрабіць.

У некаторых выпадках гэта неабходна і пажадана мець шчыльна злучаную залежнасць паміж двума суб'ектамі, а таксама ў іншых выпадках гэта не так. Калі ёсць агульнае правіла, якое ўжываецца тут, я б сказаў, калі нешта супрацьлеглае таму, што вы прапанавалі:

<�Р> Устараненне залежнасцяў ўсюды, дзе гэта магчыма, але нідзе.
18
дададзена

Там не зусім той, які універсальна правоў, а другі, што гэта універсальна няправільна. Гэта ў асноўным пытанне, які адлюстроўвае ваша рэальны намер лепш (што немагчыма адгадаць з метапеременными).

Напрыклад, калі я напісаў «read_person_data» функцыю, яна павінна, верагодна, узяць нейкі "твар" аб'ект у якасці мішэні для дадзеных ён збіраецца чытаць.

Калі ж, з другога боку, у мяне ёсць «прачытаў два радкі і ў ИНТ» функцыю, то, верагодна, варта ўзяць два радкі і ў Int, нават калі (напрыклад) я выпадкова выкарыстоўваць яго, каб прачытаць імя і прозвішча, і нумар супрацоўніка (гэта значыць, па меншай меры, частка дадзеных чалавека).

Дзве асноўных кропак, хоць: першы, функцыя як былыя, што робіць нешта значнае і лагічна, як правіла, больш пераважная, як той, што гэта крыху больш, чым любая сукупнасць дзеянняў, якія адбываюцца, адбываюцца разам.

Па-другое, калі ў вас ёсць такой сітуацыі, ён становіцца адкрытым пытанне, ці будзе функцыя ў пытанні не павінна быць (прынамсі, лагічна) частка вашага А замест таго, каб нешта асобнае, якое працуе па прынцыпе < код> А . Гэта не ўпэўнена, любыя сродкі, але вы можаце проста глядзець на бедную сегментацыі паміж класамі ў пытанні.

13
дададзена

Сардэчна запрашаем у свет тэхнікі, дзе ўсё кампраміснае і правільнае рашэнне залежыць ад таго, як класы і функцыі павінны быць выкарыстаны (і тое, што іх значэнне павінна быць).

Калі Foo() гэта <�ет> канцэптуальна нешта вынік якога залежыць ад флоат , элемент Int і радок , то гэта правільна для таго, каб прыняць паплаўка , элемент Int і радок . Прыходзяць Ці гэтыя значэння з членаў класа (можа быць B , але можа таксама быць C або D ) гэта не мае значэння, таму што семантычна Foo() не вызначаны ў тэрмінах B .

З іншага боку, калі Foo() гэта <�ет> канцэптуальна нешта вынік якога залежыць ад стану B - напрыклад, таму што ён разумее, абстракцыя над гэтай дзяржавай - то зрабіць гэта прыняць аб'ект тыпу B .

Зараз гэта таксама дакладна, што добрая практыка праграмавання, каб дазволіць функцыі прыняць невялікая колькасць аргументаў, калі гэта магчыма, я б сказаў, што да трох, без перабольшання, так што калі функцыя лагічна працуе з некалькімі значэннямі, вы можаце згрупаваць гэтыя значэння у структуры дадзеных, і перадаваць асобнікі гэтай структуры дадзеных Foo() .

Цяпер, калі вы тэлефануеце, што структура дадзеных B , мы вярнуліся да зыходнай праблеме - але з светам семантычнай розніцы!

Такім чынам, незалежна ад таго ці не Foo() павінен прымаць тры значэнні або асобнік B у асноўным залежыць ад таго, які Foo() і Б канкрэтна азначае ў вашай праграме, і як яны будуць выкарыстоўвацца - няхай гэта будзе іх паўнамоцтвы і абавязкі лагічна звязаны ці не.

5
дададзена

Улічваючы гэтае пытанне, вы, верагодна, думаеце пра класах у няправільным кірунку.

Пры выкарыстанні класа, вы павінны быць зацікаўлены толькі ў яго адкрытым інтэрфейсе (як правіла, складаюцца выключна з метадаў), а не ў сваіх сябрах дадзеных. Члены дадзеных звычайна павінны быць зачыненымі для класа ў любым выпадку, так што вы нават не маюць доступу да іх.

Падумайце аб класах як фізічныя аб'екты, скажам, шар. Вы можаце паглядзець на мяч і бачыць, што ён чырвоны, але вы не можаце проста ўсталяваць колер шара, каб быць сінім замест гэтага. Вы павінны выканаць дзеянне на мяч, каб зрабіць яго сінім, напрыклад, малюючы яго.

Назад да класаў: для таго, каб дазволіць выканаць якія-небудзь дзеянні на B, A павінен ведаць сёе-тое пра B (напрыклад, што мяч павінен быць афарбаваны, каб змяніць яго колер).

Калі вы хочаце мець працу з іншымі, чым ад класа B аб'ектамі, вы можаце выкарыстоўваць спадчыну, каб атрымаць інтэрфейс, неабходны ў класе I, а затым няхай клас B і некаторыя іншыя класа C спадчыну ад I. А зараз могуць працаваць аднолькава добра абодва класа ў і С.

3
дададзена

Ёсць некалькі прычын, чаму вы хочаце, каб абыйсці клас замест асобных членаў.

  1. Гэта залежыць ад таго, што выкліканая функцыя павінна рабіць з аргументамі. Калі аргументы Вылучаюць досыць, я б перадаць элемент.
  2. У некаторых выпадках вам можа спатрэбіцца прайсці шмат зменных. У гэтым выпадку больш эфектыўна спакаваць іх у адзін аб'ект і перадаць, што вакол.
  3. Герметызацыя. Калі вам трэба захаваць значэння нейкім чынам звязаны адзін з адным, вы можаце мець справу з класам гэтай асацыяцыі замест д ок ументами ў кодзе дзе б вы ні патрэбен адзін член яго.

Калі вы турбуецеся залежнасці aybout вы можаце рэалізаваць інтэрфейсы (напрыклад, у Java або жа ў C ++ з выкарыстаннем абстрактных класаў). Такім чынам, вы можаце паменшыць залежнасць ад канкрэтнага аб'екта, але гарантуючы, што ён можа апрацоўваць неабходны API.

2
дададзена

Я думаю, што гэта, безумоўна, залежыць ад таго, што менавіта вы хочаце дасягнуць. Там няма нічога дрэннага з праходжаннем некалькіх членаў з класа ў функцыю. Гэта сапраўды залежыць ад таго, што «Foo1» азначае - гэта робіць Foo1 на B з other_data_x зрабіць яго больш ясным, што вы хочаце зрабіць.

Калі мы возьмем прыклад - замест таго, каб мець адвольныя імёны A, B, FOO і гэтак далей, мы робім «рэальныя» імёны, якія мы можам зразумець сэнс:

enum Shapetype
{
   Circle, Square
};

enum ShapeColour
{
   Red, Green, Blue
}

class Shape 
{ 
 public:
   Shape(ShapeType type, int x, int y, ShapeColour c) :
         x(x), y(y), type(type), colour(c) {}
   ShapeType type;
   int x, y;
   ShapeColour colour;
   ...
};


class Renderer
{
   ... 
   DrawObject1(const Shape &s, float magnification); 
   DrawObject2(ShapeType s, int x, int y, ShapeColour c, float magnification); 
};


int main()
{
   Renderer r(...);
   Shape c(Circle, 10, 10, Red);
   Shape s(Square, 20, 20, Green);

   r.DrawObject1(c, 1.0);
   r.DrawObject1(s, 2.0);
  //---- or ---
   r.DrawObject2(c.type, c.x, c.y, c.colour, 1.0);
   r.DrawObject2(s.type, s.x, s.y, s.colour, 1.0);
};

[Так, гэта даволі дурны прыклад да гэтага часу, але гэта робіць трохі больш сэнсу абмяркоўваць гэтую тэму, то аб'екты маюць рэальныя імёны]

DrawObject1 needs to know everything about a Shape, and if we start doing some reorganising of the data structure (to store x and y in one member variable called point), it has to change. But it's probably just a few small changes.

З іншага боку, у DrawObject2 </кодзе>, мы можам рэарганізаваць усе мы любім з класам формы - нават выдаліць усе гэтую разам і ёсць х, у, форма і колеру асобных вектараў [ня асабліва добрая ідэя, але калі мы думаем, што гэта рашэнне некаторых праблем дзесьці, то мы можам зрабіць гэта].

Гэта ў значнай ступені зводзіцца да таго, што робіць большасць сэнсу. У маім прыкладзе, верагодна, мае сэнс перадаць Форма аб'ект DrawObject1 . Але гэта не абавязкова быць, і ёсць, вядома, шмат выпадкаў, калі вы не хацелі б гэтага.

2
дададзена

Чаму вы перадаеце аб'екты замест прымітываў?

Гэта тып пытання я б чакаць ад праграмістаў @ такой; тым не менш ..

Пераходзяць аб'екты замест прымітываў, таму што мы хочам, каб усталяваць выразны кантэкст, вылучыць пэўную кропку гледжання, і выказаць яснае намер.

Выбар прайсці прымітывы замест паўнавартасных, багатых, кантэкстных аб'ектаў паніжае ступень абстракцыі і пашырае сферу прымянення функцыі. Часам тыя мэты, але, як правіла, яны з'яўляюцца рэчы, каб пазбегнуць. Нізкая згуртаванасць з'яўляецца адказнасць, не з'яўляецца актывам.

Замест таго, каб працаваць на звязаных з імі рэчаў праз паўнавартасны, багаты аб'ект, з прымітывы зараз мы працуем на не больш чым адвольныя значэння, якія могуць або не могуць быць строга звязаны адзін з адным. Пры адсутнасці пэўнага кантэксту, мы не ведаем, калі камбінацыя прымітываў дзейнічаюць разам у любы час, не кажучы ўжо цяпер у бягучым стане сістэмы. Любыя сродкі абарона забяспечыў бы багаты кантэкст аб'ект губляецца (або прадубляваная ў няправільным месцы), калі мы выбралі прымітывы ў вызначэнні функцыі, калі мы маглі б выбраць багаты аб'ект. Акрамя таго, любыя падзеі або сігналы, якія мы звычайна б падняць, назіраць, і дзейнічаць на змены ў любыя значэнні ў багатым аб'екце кантэксту цяжэй падняць і прасачыць у патрэбны час яны маюць дачыненне пры працы з простымі прымітыўны.

Выкарыстанне аб'ектаў над прымітывы спрыяе згуртаванасці. Згуртаванасць з'яўляецца мэтай. Рэчы, якія належаць разам трымацца разам. Паважаючы натуральную залежнасць функцыі ад гэтай групоўкі, упарадкавання і ўзаемадзеяння параметраў праз ясны кантэкст добрая рэч.

Выкарыстанне аб'ектаў над прымітыву не абавязкова павялічвае від сувязі мы турбавацца больш за ўсё. Від сувязі мы павінны турбавацца больш за ўсё гэта від сувязі, якая ўзнікае, калі знешнія абаненты дыктуюць форму і паслядоўнасць паведамленняў, і мы далей аншлаг іх прадпісаным правілах, як адзіны спосаб, каб гуляць.

Замест таго, каб ісці ЮЫЫ-ін, мы павінны адзначыць выразную «сказаць», калі мы яго бачым. пастаўшчыкі Middleware і пастаўшчыкі паслуг, безумоўна, хочуць, каб пайсці ЮЫЫ-ін, і цесна інтэграваць сваю сістэму з нашай. Яны хочуць цвёрдую залежнасць, цесна пераплятаецца з нашым уласным кодам, таму мы працягваем вяртацца. Тым не менш, мы разумнейшыя, чым гэта. Кароткія, каб быць разумным, мы можам па крайняй меры, дастаткова вопытны, пабываўшы па гэтым шляху, каб прызнаць, што будзе. Мы не да стаўкі, дазваляючы элементы кода прадаўцоў вторгнуться кожны закутак, ведаючы, што мы не можам купіць руку, так як яны сядзяць на кучы фішак, і шчыра кажучы, наша рука проста не так добра. Замест гэтага, мы гаворым, што гэта тое, што я буду рабіць з прамежкавым пластом, і мы улягліся абмежаваным, адаптыўны інтэрфейс, які дазваляе гуляць працягнуць, але не стаўку фермы на гэтую адной руцэ. Мы робім гэта таму, што ў час наступнай боку, мы можам сутыкнуцца з супраць іншага прамежкавага пастаўшчыка або пастаўшчыка паслуг.

Спецыяльная покерный метафара ў бок, ідэя уцякаючы ад муфты кожны раз, калі яна ўяўляе сама па сабе будзе каштаваць вам. Запуск з найбольш пераплеценых і дарагой сувязі, верагодна, разумная рэч, каб зрабіць, калі вы збіраецеся застацца ў гульні на доўгі шлях, і маюць нахіл, што вы будзеце гуляць з іншымі пастаўшчыкамі або пастаўшчыкамі або прылад, якія вы можаце аказаць ніякага кантролю.

Існуе значна больш, я мог бы сказаць пра ўзаемасувязь кантэкстных аб'ектаў у параўнанні з выкарыстаннем прымітываў, напрыклад, у прадастаўленні змястоўных, гнуткія тэстаў. Замест гэтага, я хацеў бы прапанаваць канкрэтныя паказанні аб кадаванні стылю ад аўтараў, як Uncle Bob Martin, але я апушчу іх цяпер.

1
дададзена

Згаджаючыся з першым рэспандэнтам тут, і на ваша пытанне аб тым, «ёсць іншы шлях ...»;

Так як вы перадаеце ў экзэмпляры класа, B, да метаду Foo1 А разумна выказаць здагадку, што функцыянальнасць B забяспечвае не цалкам унікальным для яго, гэта значыць могуць быць і іншыя спосабы рэалізацыі любой B забяспечвае A . (Калі B з'яўляецца POD, без логікі яго ўласнай, то ён нават не мае сэнсу спрабаваць раз'яднаць іх, так як а трэба шмат ведаць аб ў любым выпадку).

Такім чынам, вы маглі б раз'яднаць А з брудных сакрэтаў Б, падымаючы ўсё, што B робіць для А да інтэрфейсу, а затым ўключае «BsInterface.h» замест гэтага. Гэта прадугледжвае, што можа быць C, D ... і іншыя варыянты рабіць тое, што B робіць для A. Калі не , то вы павінны спытаць сябе, чаму B клас па-за А ў першую чаргу ...

У рэшце рэшт, усё зводзіцца да адной рэчы; Ён павінен мець сэнс ...

I always turn the problem on its head when I run into philosophical debates like this (with myself, mostly); how would I be using A->FOO1 ? Does it make sense, in the calling code, to deal with B and do I always end up including A and B together anyway, because of the way A and B are used together?

гэта значыць; калі вы хочаце быць пераборлівыя і пісаць чысты код (+1 да гэтага), а затым зрабіць крок назад і прымяніць «трымаць яго проста» правілы, але заўсёды дазваляюць практычнасць адмяняць любыя спакусы, якія Вы павінны больш стварыць свой код.

Ну, гэта маё меркаванне ў любым выпадку.

0
дададзена